#!/usr/bin/env python3
"""
Exploit knowledge base.
Maps services and versions to known vulnerabilities and MSF modules.
"""
from typing import List, Dict, Optional
import re
import subprocess
import json
import time
import os


# Exploit database with known vulnerabilities
EXPLOIT_DATABASE = {
    'ssh': {
        'patterns': [
            {
                'product': 'OpenSSH',
                'version_range': '<7.4',
                'cve': 'CVE-2016-10009',
                'title': 'OpenSSH Username Enumeration',
                'msf_module': 'auxiliary/scanner/ssh/ssh_enumusers',
                'severity': 'medium',
                'description': 'Username enumeration via timing attack in OpenSSH < 7.4'
            },
            {
                'product': 'OpenSSH',
                'version_range': 'all',
                'cve': None,
                'title': 'SSH Login Brute Force',
                'msf_module': 'auxiliary/scanner/ssh/ssh_login',
                'severity': 'high',
                'description': 'Attempt credential brute force attack'
            },
            {
                'product': 'OpenSSH',
                'version_range': 'all',
                'cve': None,
                'title': 'SSH Key Enumeration',
                'msf_module': 'auxiliary/scanner/ssh/ssh_enumusers',
                'severity': 'low',
                'description': 'Enumerate valid SSH users'
            }
        ],
        'techniques': [
            {
                'name': 'SSH Key Enumeration',
                'msf_modules': ['auxiliary/scanner/ssh/ssh_enumusers'],
                'severity': 'low'
            },
            {
                'name': 'Password Spray',
                'msf_modules': ['auxiliary/scanner/ssh/ssh_login'],
                'severity': 'high'
            },
            {
                'name': 'SSH Tunneling',
                'msf_modules': ['auxiliary/scanner/ssh/detect_kippo'],
                'severity': 'medium'
            },
            {
                'name': 'Weak Crypto Detection',
                'msf_modules': ['auxiliary/scanner/ssh/ssh_version'],
                'severity': 'low'
            }
        ]
    },
    'ftp': {
        'patterns': [
            {
                'product': 'vsftpd',
                'version_exact': '2.3.4',
                'cve': 'CVE-2011-2523',
                'title': 'vsftpd 2.3.4 Backdoor Command Execution',
                'msf_module': 'exploit/unix/ftp/vsftpd_234_backdoor',
                'severity': 'critical',
                'description': 'Backdoor in vsftpd 2.3.4 allows arbitrary command execution via :) in username'
            },
            {
                'product': 'ProFTPD',
                'version_range': '1.3.3c',
                'cve': 'CVE-2010-4221',
                'title': 'ProFTPD Telnet IAC Buffer Overflow',
                'msf_module': 'exploit/unix/ftp/proftpd_telnet_iac',
                'severity': 'high',
                'description': 'Stack-based buffer overflow in ProFTPD 1.3.2-1.3.3b'
            },
            {
                'product': 'ProFTPD',
                'version_range': '1.3.5',
                'cve': 'CVE-2015-3306',
                'title': 'ProFTPD mod_copy Command Execution',
                'msf_module': 'exploit/unix/ftp/proftpd_modcopy_exec',
                'severity': 'critical',
                'description': 'Unauthenticated copying of files via SITE CPFR/CPTO commands'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'FTP Anonymous Login',
                'msf_module': 'auxiliary/scanner/ftp/anonymous',
                'severity': 'medium',
                'description': 'Check for anonymous FTP access'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'FTP Login Brute Force',
                'msf_module': 'auxiliary/scanner/ftp/ftp_login',
                'severity': 'high',
                'description': 'Attempt credential brute force'
            }
        ],
        'techniques': [
            {
                'name': 'Anonymous FTP Access',
                'msf_modules': ['auxiliary/scanner/ftp/anonymous'],
                'severity': 'medium'
            },
            {
                'name': 'FTP Bounce Attack',
                'msf_modules': ['auxiliary/scanner/ftp/ftp_version'],
                'severity': 'medium'
            },
            {
                'name': 'Credential Brute Force',
                'msf_modules': ['auxiliary/scanner/ftp/ftp_login'],
                'severity': 'high'
            },
            {
                'name': 'Directory Traversal',
                'msf_modules': ['auxiliary/scanner/ftp/anonymous'],
                'severity': 'medium'
            }
        ]
    },
    'mysql': {
        'patterns': [
            {
                'product': 'MySQL',
                'version_range': '<5.0.83',
                'cve': 'CVE-2009-2446',
                'title': 'MySQL yaSSL Buffer Overflow',
                'msf_module': 'exploit/linux/mysql/mysql_yassl_getname',
                'severity': 'critical',
                'description': 'Remote code execution via yaSSL SSL_get_peer_name buffer overflow'
            },
            {
                'product': 'MySQL',
                'version_range': '<5.1.63',
                'cve': 'CVE-2012-2122',
                'title': 'MySQL Authentication Bypass',
                'msf_module': 'auxiliary/scanner/mysql/mysql_authbypass_hashdump',
                'severity': 'critical',
                'description': 'Authentication bypass allows login with any password'
            },
            {
                'product': 'MySQL',
                'version_range': 'all',
                'cve': None,
                'title': 'MySQL Login Brute Force',
                'msf_module': 'auxiliary/scanner/mysql/mysql_login',
                'severity': 'medium',
                'description': 'Attempt credential brute force'
            },
            {
                'product': 'MySQL',
                'version_range': 'all',
                'cve': None,
                'title': 'MySQL Schema Enumeration',
                'msf_module': 'auxiliary/admin/mysql/mysql_enum',
                'severity': 'low',
                'description': 'Enumerate databases, tables, and users'
            },
            {
                'product': 'MySQL',
                'version_range': 'all',
                'cve': None,
                'title': 'MySQL SQL Query',
                'msf_module': 'auxiliary/admin/mysql/mysql_sql',
                'severity': 'high',
                'description': 'Execute arbitrary SQL queries'
            }
        ],
        'techniques': [
            {
                'name': 'UDF Injection',
                'msf_modules': ['exploit/linux/mysql/mysql_udf_payload', 'exploit/windows/mysql/mysql_payload'],
                'severity': 'critical'
            },
            {
                'name': 'File Read (load_file)',
                'msf_modules': ['auxiliary/admin/mysql/mysql_sql'],
                'severity': 'high'
            },
            {
                'name': 'Credential Theft',
                'msf_modules': ['auxiliary/scanner/mysql/mysql_hashdump'],
                'severity': 'high'
            },
            {
                'name': 'SQL Injection',
                'msf_modules': ['auxiliary/scanner/http/sqlmap'],
                'severity': 'high'
            }
        ]
    },
    'http': {
        'patterns': [
            {
                'product': 'Apache',
                'version_range': '2.4.49-2.4.50',
                'cve': 'CVE-2021-41773',
                'title': 'Apache HTTP Server Path Traversal RCE',
                'msf_module': 'exploit/multi/http/apache_normalize_path_rce',
                'severity': 'critical',
                'description': 'Path traversal and RCE in Apache 2.4.49-2.4.50'
            },
            {
                'product': 'Apache',
                'version_range': '2.2.0-2.2.21',
                'cve': 'CVE-2011-3192',
                'title': 'Apache Range Header DoS',
                'msf_module': 'auxiliary/dos/http/apache_range_dos',
                'severity': 'medium',
                'description': 'Denial of Service via Range header'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'HTTP Directory Scanner',
                'msf_module': 'auxiliary/scanner/http/dir_scanner',
                'severity': 'low',
                'description': 'Scan for common directories and files'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'HTTP Title Grabber',
                'msf_module': 'auxiliary/scanner/http/title',
                'severity': 'info',
                'description': 'Extract HTTP page titles for recon'
            }
        ],
        'techniques': [
            {
                'name': 'SQL Injection',
                'msf_modules': ['auxiliary/scanner/http/sqlmap'],
                'severity': 'high'
            },
            {
                'name': 'XSS',
                'msf_modules': ['auxiliary/scanner/http/xpath'],
                'severity': 'medium'
            },
            {
                'name': 'Directory Traversal',
                'msf_modules': ['auxiliary/scanner/http/dir_scanner', 'auxiliary/scanner/http/dir_listing'],
                'severity': 'medium'
            },
            {
                'name': 'File Upload',
                'msf_modules': ['exploit/multi/http/php_file_upload'],
                'severity': 'high'
            },
            {
                'name': 'Command Injection',
                'msf_modules': ['auxiliary/scanner/http/http_put'],
                'severity': 'critical'
            },
            {
                'name': 'SSRF',
                'msf_modules': ['auxiliary/scanner/http/wordpress_ssrf'],
                'severity': 'high'
            }
        ]
    },
    'https': {
        'patterns': [
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SSL/TLS Scanner',
                'msf_module': 'auxiliary/scanner/ssl/ssl_version',
                'severity': 'low',
                'description': 'Enumerate SSL/TLS versions and ciphers'
            },
            {
                'product': 'OpenSSL',
                'version_range': '1.0.1-1.0.1f',
                'cve': 'CVE-2014-0160',
                'title': 'Heartbleed SSL Memory Disclosure',
                'msf_module': 'auxiliary/scanner/ssl/openssl_heartbleed',
                'severity': 'critical',
                'description': 'OpenSSL Heartbleed vulnerability allows memory disclosure'
            }
        ],
        'techniques': [
            {
                'name': 'SSL Stripping',
                'msf_modules': ['auxiliary/scanner/ssl/ssl_version'],
                'severity': 'medium'
            },
            {
                'name': 'Weak Cipher Exploitation',
                'msf_modules': ['auxiliary/scanner/ssl/ssl_version'],
                'severity': 'high'
            },
            {
                'name': 'Certificate Spoofing',
                'msf_modules': ['auxiliary/gather/ssl_heartbleed'],
                'severity': 'high'
            }
        ]
    },
    'smb': {
        'patterns': [
            {
                'product': 'Samba',
                'version_range': '3.0.20-3.0.25',
                'cve': 'CVE-2007-2447',
                'title': 'Samba "username map script" Command Execution',
                'msf_module': 'exploit/multi/samba/usermap_script',
                'severity': 'critical',
                'description': 'Command injection in username parameter allows RCE'
            },
            {
                'product': 'Samba',
                'version_range': '3.5.0-4.6.4',
                'cve': 'CVE-2017-7494',
                'title': 'SambaCry Remote Code Execution',
                'msf_module': 'exploit/linux/samba/is_known_pipename',
                'severity': 'critical',
                'description': 'Remote code execution via uploaded shared library'
            },
            {
                'product': 'Windows',
                'version_range': 'all',
                'cve': 'CVE-2017-0143',
                'title': 'EternalBlue SMB Remote Code Execution',
                'msf_module': 'exploit/windows/smb/ms17_010_eternalblue',
                'severity': 'critical',
                'description': 'SMBv1 vulnerability (MS17-010) allows remote code execution'
            },
            {
                'product': 'Windows',
                'version_range': 'all',
                'cve': 'CVE-2017-0144',
                'title': 'EternalBlue DoublePulsar Check',
                'msf_module': 'auxiliary/scanner/smb/smb_ms17_010',
                'severity': 'critical',
                'description': 'Check if system is vulnerable to MS17-010'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SMB Version Detection',
                'msf_module': 'auxiliary/scanner/smb/smb_version',
                'severity': 'info',
                'description': 'Detect SMB version and OS information'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SMB Share Enumeration',
                'msf_module': 'auxiliary/scanner/smb/smb_enumshares',
                'severity': 'low',
                'description': 'Enumerate SMB shares'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SMB Login Brute Force',
                'msf_module': 'auxiliary/scanner/smb/smb_login',
                'severity': 'high',
                'description': 'Attempt credential brute force'
            }
        ],
        'techniques': [
            {
                'name': 'Share Enumeration',
                'msf_modules': ['auxiliary/scanner/smb/smb_enumshares'],
                'severity': 'low'
            },
            {
                'name': 'Credential Brute Force',
                'msf_modules': ['auxiliary/scanner/smb/smb_login'],
                'severity': 'high'
            },
            {
                'name': 'Pass-the-Hash',
                'msf_modules': ['exploit/windows/smb/psexec'],
                'severity': 'critical'
            },
            {
                'name': 'SMB Relay Attack',
                'msf_modules': ['auxiliary/server/capture/smb'],
                'severity': 'high'
            },
            {
                'name': 'Null Session',
                'msf_modules': ['auxiliary/scanner/smb/smb_enumshares'],
                'severity': 'medium'
            }
        ]
    },
    'postgres': {
        'patterns': [
            {
                'product': 'PostgreSQL',
                'version_range': 'all',
                'cve': None,
                'title': 'PostgreSQL Login Brute Force',
                'msf_module': 'auxiliary/scanner/postgres/postgres_login',
                'severity': 'medium',
                'description': 'Attempt credential brute force'
            },
            {
                'product': 'PostgreSQL',
                'version_range': 'all',
                'cve': None,
                'title': 'PostgreSQL Database Enumeration',
                'msf_module': 'auxiliary/admin/postgres/postgres_sql',
                'severity': 'low',
                'description': 'Execute arbitrary SQL queries'
            },
            {
                'product': 'PostgreSQL',
                'version_range': '<9.3',
                'cve': 'CVE-2013-1899',
                'title': 'PostgreSQL COPY FROM PROGRAM Command Execution',
                'msf_module': 'exploit/linux/postgres/postgres_payload',
                'severity': 'high',
                'description': 'Execute OS commands via COPY FROM PROGRAM'
            }
        ],
        'techniques': [
            {
                'name': 'SQL Injection',
                'msf_modules': ['auxiliary/admin/postgres/postgres_sql'],
                'severity': 'high'
            },
            {
                'name': 'Command Execution (COPY FROM PROGRAM)',
                'msf_modules': ['exploit/linux/postgres/postgres_payload'],
                'severity': 'critical'
            },
            {
                'name': 'File Read/Write',
                'msf_modules': ['auxiliary/admin/postgres/postgres_readfile'],
                'severity': 'high'
            },
            {
                'name': 'Privilege Escalation',
                'msf_modules': ['exploit/linux/postgres/postgres_payload'],
                'severity': 'high'
            }
        ]
    },
    'telnet': {
        'patterns': [
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'Telnet Login Brute Force',
                'msf_module': 'auxiliary/scanner/telnet/telnet_login',
                'severity': 'high',
                'description': 'Attempt credential brute force'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'Telnet Version Detection',
                'msf_module': 'auxiliary/scanner/telnet/telnet_version',
                'severity': 'info',
                'description': 'Detect telnet service version'
            }
        ],
        'techniques': [
            {
                'name': 'Credential Brute Force',
                'msf_modules': ['auxiliary/scanner/telnet/telnet_login'],
                'severity': 'high'
            },
            {
                'name': 'Session Hijacking',
                'msf_modules': ['auxiliary/scanner/telnet/telnet_version'],
                'severity': 'medium'
            },
            {
                'name': 'Traffic Sniffing',
                'msf_modules': ['auxiliary/sniffer/psnuffle'],
                'severity': 'medium'
            }
        ]
    },
    'smtp': {
        'patterns': [
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SMTP User Enumeration',
                'msf_module': 'auxiliary/scanner/smtp/smtp_enum',
                'severity': 'low',
                'description': 'Enumerate valid SMTP users via VRFY/EXPN/RCPT'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'SMTP Version Detection',
                'msf_module': 'auxiliary/scanner/smtp/smtp_version',
                'severity': 'info',
                'description': 'Detect SMTP service version'
            }
        ],
        'techniques': [
            {
                'name': 'User Enumeration',
                'msf_modules': ['auxiliary/scanner/smtp/smtp_enum'],
                'severity': 'low'
            },
            {
                'name': 'Email Spoofing',
                'msf_modules': ['auxiliary/scanner/smtp/smtp_relay'],
                'severity': 'medium'
            },
            {
                'name': 'Open Relay Abuse',
                'msf_modules': ['auxiliary/scanner/smtp/smtp_relay'],
                'severity': 'high'
            }
        ]
    },
    'rdp': {
        'patterns': [
            {
                'product': 'Windows',
                'version_range': 'all',
                'cve': 'CVE-2019-0708',
                'title': 'BlueKeep RDP Remote Code Execution',
                'msf_module': 'exploit/windows/rdp/cve_2019_0708_bluekeep_rce',
                'severity': 'critical',
                'description': 'Pre-authentication RCE in Remote Desktop Services'
            },
            {
                'product': 'all',
                'version_range': 'all',
                'cve': None,
                'title': 'RDP Login Brute Force',
                'msf_module': 'auxiliary/scanner/rdp/rdp_scanner',
                'severity': 'high',
                'description': 'Scan for RDP services and attempt login'
            }
        ],
        'techniques': [
            {
                'name': 'Credential Brute Force',
                'msf_modules': ['auxiliary/scanner/rdp/rdp_scanner'],
                'severity': 'high'
            },
            {
                'name': 'Session Hijacking',
                'msf_modules': ['auxiliary/scanner/rdp/rdp_scanner'],
                'severity': 'high'
            },
            {
                'name': 'Pass-the-Hash',
                'msf_modules': ['exploit/windows/local/ms14_068_kerberos_checksum'],
                'severity': 'critical'
            }
        ]
    }
}


# SearchSploit cache - {query: (results, timestamp)}
_SEARCHSPLOIT_CACHE = {}
_SEARCHSPLOIT_CACHE_TIMEOUT = 3600  # 1 hour - searchsploit DB doesn't change often


class ExploitKnowledgeBase:
    """Match services to known exploits and techniques."""

    def __init__(self, use_searchsploit: bool = True):
        """
        Initialize exploit knowledge base.

        Args:
            use_searchsploit: If True, augment static KB with searchsploit queries (default: True)
        """
        self.database = EXPLOIT_DATABASE
        self.use_searchsploit = use_searchsploit and self._searchsploit_available()

    def _searchsploit_available(self) -> bool:
        """Check if searchsploit is installed and available."""
        try:
            subprocess.run(['searchsploit', '--help'],
                          capture_output=True,
                          timeout=2,
                          check=False)
            return True
        except (FileNotFoundError, subprocess.TimeoutExpired):
            return False
    
    def find_exploits(self, service: str, version: str, product: str = None) -> List[Dict]:
        """
        Find matching exploits for a service.

        Args:
            service: Service name (e.g., 'ssh', 'mysql')
            version: Version number (e.g., '2.3.4', '8.2')
            product: Product name (e.g., 'OpenSSH', 'vsftpd')

        Returns:
            List of matching exploits with metadata
        """
        service_lower = service.lower()

        # Get patterns from static knowledge base
        matches = []
        if service_lower in self.database:
            service_data = self.database[service_lower]
            patterns = service_data.get('patterns', [])

            for pattern in patterns:
                match_type = self._check_match(pattern, version, product)

                if match_type:
                    exploit = {
                        **pattern,
                        'match_type': match_type,
                        'source': 'msf_kb'
                    }
                    matches.append(exploit)

        # Augment with searchsploit results if enabled
        if self.use_searchsploit:
            searchsploit_results = self._query_searchsploit(service, version, product)
            matches.extend(searchsploit_results)

        # Deduplicate and rank by priority
        matches = self._deduplicate_exploits(matches)
        return self._rank_exploits(matches)
    
    def get_techniques(self, service: str) -> List[str]:
        """Get exploitation techniques for a service type."""
        service_lower = service.lower()

        if service_lower in self.database:
            return self.database[service_lower].get('techniques', [])

        return []

    def _query_searchsploit(self, service: str, version: str, product: str = None) -> List[Dict]:
        """
        Query searchsploit for exploits.

        Args:
            service: Service name
            version: Version number
            product: Product name

        Returns:
            List of exploits from searchsploit
        """
        # Build smart query - use product+version if available, otherwise service
        if product and version and version != 'unknown':
            query = f"{product} {version}"
        elif product:
            query = product
        elif version and version != 'unknown':
            query = f"{service} {version}"
        else:
            query = service

        # Check cache first
        cache_key = query.lower()
        if cache_key in _SEARCHSPLOIT_CACHE:
            cached_results, cached_time = _SEARCHSPLOIT_CACHE[cache_key]
            if time.time() - cached_time < _SEARCHSPLOIT_CACHE_TIMEOUT:
                return cached_results

        # Run searchsploit
        try:
            result = subprocess.run(
                ['searchsploit', '--json', query],
                capture_output=True,
                timeout=5,  # Reduced to 5s to prevent UI hangs
                text=True,
                check=False
            )

            if result.returncode != 0:
                return []

            # Parse JSON output
            data = json.loads(result.stdout)
            exploits = []

            # Parse exploit results
            for exploit_result in data.get('RESULTS_EXPLOIT', []):
                exploit = self._parse_searchsploit_result(exploit_result, service, version, product)
                if exploit:
                    exploits.append(exploit)

            # Parse shellcode results (less common but useful)
            for shellcode_result in data.get('RESULTS_SHELLCODE', []):
                exploit = self._parse_searchsploit_result(shellcode_result, service, version, product, is_shellcode=True)
                if exploit:
                    exploits.append(exploit)

            # Cache results
            _SEARCHSPLOIT_CACHE[cache_key] = (exploits, time.time())

            return exploits

        except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
            # Graceful degradation - if searchsploit fails, just return empty
            return []

    def _parse_searchsploit_result(self, result: Dict, service: str, version: str,
                                   product: str = None, is_shellcode: bool = False) -> Optional[Dict]:
        """
        Parse a single searchsploit result and convert to our format.

        Args:
            result: Raw searchsploit result
            service: Service name
            version: Version number
            product: Product name
            is_shellcode: Whether this is shellcode (vs exploit)

        Returns:
            Formatted exploit dict or None if should be filtered
        """
        title = result.get('Title', '')
        platform = result.get('Platform', result.get('Type', ''))
        edb_id = result.get('EDB-ID', '')

        # Filter out irrelevant platforms for better signal-to-noise
        # e.g., if we're scanning a Linux host, skip Windows exploits
        # For now, we'll include everything but could add smarter filtering later

        # Determine match type and severity
        match_type = self._determine_searchsploit_match_type(title, version, product)
        severity = self._determine_searchsploit_severity(title, platform)

        # Extract CVE if mentioned in title
        cve = None
        cve_match = re.search(r'CVE-\d{4}-\d{4,}', title, re.IGNORECASE)
        if cve_match:
            cve = cve_match.group(0).upper()

        return {
            'title': title,
            'severity': severity,
            'msf_module': None,  # searchsploit results aren't MSF modules
            'cve': cve,
            'description': f"Exploit-DB entry for {product or service}",
            'match_type': match_type,
            'source': 'searchsploit',
            'edb_id': edb_id,
            'edb_url': f"https://www.exploit-db.com/exploits/{edb_id}" if edb_id else None,
            'platform': platform,
            'path': result.get('Path', ''),
            'is_shellcode': is_shellcode
        }

    def _determine_searchsploit_match_type(self, title: str, version: str, product: str) -> str:
        """Determine how well the searchsploit result matches our query."""
        title_lower = title.lower()

        # Check if exact version is in title
        if version and version != 'unknown' and version in title_lower:
            return 'exact'

        # Check if product is in title
        if product and product.lower() in title_lower:
            return 'range'

        # Generic match
        return 'generic'

    def _determine_searchsploit_severity(self, title: str, platform: str) -> str:
        """
        Determine severity based on exploit title keywords.

        This is heuristic-based since searchsploit doesn't provide severity ratings.
        """
        title_lower = title.lower()

        # Critical indicators
        critical_keywords = ['remote code execution', 'rce', 'backdoor', 'unauthenticated',
                           'pre-auth', 'arbitrary code', 'command injection', 'command execution']
        if any(keyword in title_lower for keyword in critical_keywords):
            return 'critical'

        # High indicators
        high_keywords = ['privilege escalation', 'buffer overflow', 'sql injection',
                        'authentication bypass', 'file upload', 'arbitrary file']
        if any(keyword in title_lower for keyword in high_keywords):
            return 'high'

        # Medium indicators
        medium_keywords = ['denial of service', 'dos', 'xss', 'csrf', 'information disclosure',
                          'directory traversal', 'path traversal']
        if any(keyword in title_lower for keyword in medium_keywords):
            return 'medium'

        # Default to low for everything else
        return 'low'

    def _deduplicate_exploits(self, exploits: List[Dict]) -> List[Dict]:
        """
        Remove duplicate exploits based on CVE or title similarity.

        Prioritizes MSF modules over searchsploit results when duplicates exist.

        Args:
            exploits: List of exploits from multiple sources

        Returns:
            Deduplicated list
        """
        seen_cves = set()
        seen_titles = set()
        deduplicated = []

        # Sort so MSF results come first (they'll be kept in case of duplicates)
        exploits_sorted = sorted(exploits, key=lambda x: 0 if x.get('source') == 'msf_kb' else 1)

        for exploit in exploits_sorted:
            # Check CVE deduplication
            cve = exploit.get('cve')
            if cve and cve in seen_cves:
                continue

            # Check title similarity (fuzzy matching)
            title = exploit.get('title', '').lower()
            # Normalize title for comparison
            title_normalized = re.sub(r'[^\w\s]', '', title)

            is_duplicate = False
            for seen_title in seen_titles:
                # If titles are very similar (>80% overlap), consider duplicate
                if self._title_similarity(title_normalized, seen_title) > 0.8:
                    is_duplicate = True
                    break

            if is_duplicate:
                continue

            # Not a duplicate - add it
            if cve:
                seen_cves.add(cve)
            seen_titles.add(title_normalized)
            deduplicated.append(exploit)

        return deduplicated

    def _title_similarity(self, title1: str, title2: str) -> float:
        """
        Calculate simple similarity ratio between two titles.

        Returns:
            Float between 0.0 (no similarity) and 1.0 (identical)
        """
        words1 = set(title1.split())
        words2 = set(title2.split())

        if not words1 or not words2:
            return 0.0

        intersection = words1.intersection(words2)
        union = words1.union(words2)

        return len(intersection) / len(union)

    def _check_match(self, pattern: Dict, version: str, product: str) -> Optional[str]:
        """
        Check if service matches exploit pattern.
        
        Returns:
            'exact' | 'range' | 'generic' | None
        """
        # Check product match
        pattern_product = pattern.get('product', 'all')
        if pattern_product != 'all' and product:
            if pattern_product.lower() != product.lower():
                return None
        
        # Check version match
        if pattern.get('version_exact'):
            if version == pattern['version_exact']:
                return 'exact'
            return None
        
        if pattern.get('version_range'):
            version_range = pattern['version_range']
            
            if version_range == 'all':
                return 'generic'
            
            if self.version_matches(version, version_range):
                return 'range'
            return None
        
        # No version constraint = generic match
        return 'generic'
    
    def version_matches(self, target_version: str, version_spec: str) -> bool:
        """
        Check if version matches specification.
        
        Examples:
            version_matches('2.3.4', '2.3.4') → True (exact)
            version_matches('7.2', '<7.4') → True (range)
            version_matches('8.2', 'all') → True (any)
        """
        if version_spec == 'all':
            return True
        
        if target_version == 'unknown':
            return False
        
        # Exact match
        if version_spec == target_version:
            return True
        
        # Range match
        if '-' in version_spec:
            # Format: "3.0.20-3.0.25"
            try:
                start, end = version_spec.split('-')
                return self._version_compare(target_version, start) >= 0 and \
                       self._version_compare(target_version, end) <= 0
            except:
                return False
        
        # Less than
        if version_spec.startswith('<'):
            compare_version = version_spec[1:]
            return self._version_compare(target_version, compare_version) < 0
        
        # Greater than
        if version_spec.startswith('>'):
            compare_version = version_spec[1:]
            return self._version_compare(target_version, compare_version) > 0
        
        return False
    
    def _version_compare(self, v1: str, v2: str) -> int:
        """
        Compare two version numbers.
        
        Returns:
            -1 if v1 < v2
             0 if v1 == v2
             1 if v1 > v2
        """
        try:
            parts1 = [int(x) for x in v1.replace('p', '.').split('.')]
            parts2 = [int(x) for x in v2.replace('p', '.').split('.')]
            
            # Pad with zeros
            max_len = max(len(parts1), len(parts2))
            parts1 += [0] * (max_len - len(parts1))
            parts2 += [0] * (max_len - len(parts2))
            
            for p1, p2 in zip(parts1, parts2):
                if p1 < p2:
                    return -1
                elif p1 > p2:
                    return 1
            
            return 0
        except:
            return 0
    
    def _rank_exploits(self, exploits: List[Dict]) -> List[Dict]:
        """
        Rank exploits by priority.

        Ranking factors:
        1. Severity (critical > high > medium > low > info)
        2. Match type (exact > range > generic)
        3. Source (MSF modules preferred over searchsploit)
        4. CVE availability
        """
        severity_order = {
            'critical': 5,
            'high': 4,
            'medium': 3,
            'low': 2,
            'info': 1
        }

        match_order = {
            'exact': 3,
            'range': 2,
            'generic': 1
        }

        def sort_key(exploit):
            severity_score = severity_order.get(exploit.get('severity', 'info'), 0)
            match_score = match_order.get(exploit.get('match_type', 'generic'), 0)
            # Prefer MSF modules (more actionable) over searchsploit results
            source_score = 1 if exploit.get('source') == 'msf_kb' else 0
            cve_score = 1 if exploit.get('cve') else 0

            return (severity_score, match_score, source_score, cve_score)

        return sorted(exploits, key=sort_key, reverse=True)
