import re from typing import Optional from .prober import ProbeResult def _version_compare(extracted: str, operator: str, threshold: str) -> bool: def to_tuple(v: str): try: return tuple(int(x) for x in v.split(".")) except ValueError: return (v,) ev = to_tuple(extracted) tv = to_tuple(threshold) if operator == "<=": return ev <= tv elif operator == ">=": return ev >= tv elif operator == "<": return ev < tv elif operator == ">": return ev > tv elif operator == "==": return ev == tv elif operator == "!=": return ev != tv return False def _get_search_text(result: ProbeResult, match_in) -> str: if isinstance(match_in, list): parts = [] if "body" in match_in: parts.append(result.body or result.banner) if "headers" in match_in: parts.append(" ".join(f"{k}: {v}" for k, v in result.headers.items())) return "\n".join(parts) if match_in == "headers": return " ".join(f"{k}: {v}" for k, v in result.headers.items()) return result.body or result.banner def match_probe(result: ProbeResult, probe: dict) -> tuple[bool, Optional[str]]: if not result.success: return False, None match_in = probe.get("match_in", "body") text = _get_search_text(result, match_in) patterns = probe.get("patterns", []) all_patterns = probe.get("all_patterns", []) if patterns: if not any(re.search(p, text) for p in patterns): return False, None if all_patterns: if not all(re.search(p, text) for p in all_patterns): return False, None extracted_version = None version_extract = probe.get("version_extract") if version_extract: m = re.search(version_extract, text) if m: extracted_version = m.group(1) version_compare = probe.get("version_compare") if version_compare and extracted_version: op = version_compare.get("operator", "==") val = str(version_compare.get("value", "")) if not _version_compare(extracted_version, op, val): return False, None elif version_compare and not extracted_version: return False, None return True, extracted_version def match_target(host: str, port: int, probe_results: list[tuple[dict, ProbeResult]]) -> list[dict]: matches = [] for probe, result in probe_results: matched, version = match_probe(result, probe) if matched: matches.append({ "probe_type": result.probe_type, "confidence": probe.get("confidence", "medium"), "version": version, }) return matches