diff --git a/app.py b/app.py index 3a7361a..043205c 100644 --- a/app.py +++ b/app.py @@ -115,27 +115,11 @@ def start_scan(): user_session_id, scanner = get_user_scanner() print(f"Using session: {user_session_id}") print(f"Scanner object ID: {id(scanner)}") - print(f"Scanner status before start: {scanner.status}") - - # Additional safety check - if scanner is somehow in running state, force reset - if scanner.status == 'running': - print(f"WARNING: Scanner in session {user_session_id} was already running - forcing reset") - scanner.stop_scan() - # Give it a moment to stop - import time - time.sleep(1) - - # If still running, force status reset - if scanner.status == 'running': - print("WARNING: Force resetting scanner status from 'running' to 'idle'") - scanner.status = 'idle' # Start scan print(f"Calling start_scan on scanner {id(scanner)}...") success = scanner.start_scan(target_domain, max_depth, clear_graph=clear_graph) - print(f"scanner.start_scan returned: {success}") - print(f"Scanner status after start attempt: {scanner.status}") if success: scan_session_id = scanner.logger.session_id diff --git a/core/scanner.py b/core/scanner.py index d6fb689..9178b60 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -145,44 +145,36 @@ class Scanner: def start_scan(self, target_domain: str, max_depth: int = 2, clear_graph: bool = True) -> bool: """ - Start a new reconnaissance scan with concurrent processing. - Enhanced with better debugging and state validation. - - Args: - target_domain: Initial domain to investigate - max_depth: Maximum recursion depth - - Returns: - bool: True if scan started successfully + Start a new reconnaissance scan. + Forcefully cleans up any previous scan thread before starting. """ print(f"=== STARTING SCAN IN SCANNER {id(self)} ===") - print(f"Scanner status: {self.status}") - print(f"Target domain: '{target_domain}', Max depth: {max_depth}") - print(f"Available providers: {len(self.providers) if hasattr(self, 'providers') else 0}") - - try: - if self.status == ScanStatus.RUNNING: - print(f"ERROR: Scan already running in scanner {id(self)}, rejecting new scan") - print(f"Current target: {self.current_target}") - print(f"Current depth: {self.current_depth}") - return False + print(f"Initial scanner status: {self.status}") + # If a thread is still alive from a previous scan, we must wait for it to die. + if self.scan_thread and self.scan_thread.is_alive(): + print("A previous scan thread is still alive. Sending termination signal and waiting...") + self.stop_scan() + self.scan_thread.join(10.0) # Wait up to 10 seconds + + if self.scan_thread.is_alive(): + print("ERROR: The previous scan thread is unresponsive and could not be stopped. Please restart the application.") + self.status = ScanStatus.FAILED + return False + print("Previous scan thread terminated successfully.") + + # Reset state for the new scan + self.status = ScanStatus.IDLE + print(f"Scanner state is now clean for a new scan.") + + try: # Check if we have any providers if not hasattr(self, 'providers') or not self.providers: print(f"ERROR: No providers available in scanner {id(self)}, cannot start scan") return False - + print(f"Scanner {id(self)} validation passed, providers available: {[p.get_name() for p in self.providers]}") - # Stop any existing scan thread - if self.scan_thread and self.scan_thread.is_alive(): - print(f"Stopping existing scan thread in scanner {id(self)}...") - self.stop_event.set() - self.scan_thread.join(timeout=5.0) - if self.scan_thread.is_alive(): - print(f"WARNING: Could not stop existing thread in scanner {id(self)}") - return False - if clear_graph: self.graph.clear() self.current_target = target_domain.lower().strip() @@ -212,6 +204,7 @@ class Scanner: except Exception as e: print(f"ERROR: Exception in start_scan for scanner {id(self)}: {e}") traceback.print_exc() + self.status = ScanStatus.FAILED return False def _execute_scan(self, target_domain: str, max_depth: int) -> None: @@ -525,7 +518,13 @@ class Scanner: """ print(f"Large number of {rel_type.name} relationships for {source}. Creating a large entity node.") entity_name = f"Large collection of {rel_type.name} for {source}" - self.graph.add_node(entity_name, NodeType.LARGE_ENTITY, metadata={"count": len(targets)}) + node_type = 'unknown' + if targets: + if _is_valid_domain(targets[0]): + node_type = 'domain' + elif _is_valid_ip(targets[0]): + node_type = 'ip' + self.graph.add_node(entity_name, NodeType.LARGE_ENTITY, metadata={"count": len(targets), "nodes": targets, "node_type": node_type}) self.graph.add_edge(source, entity_name, rel_type, 0.9, provider_name, {"info": "Aggregated node"}) def _safe_provider_query(self, provider, target: str, is_ip: bool) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]: @@ -543,32 +542,27 @@ class Scanner: def stop_scan(self) -> bool: """ - Request immediate scan termination with aggressive cancellation. + Request immediate scan termination. + Acts on the thread's liveness, not just the 'RUNNING' status. """ try: - if self.status == ScanStatus.RUNNING: - print("=== INITIATING IMMEDIATE SCAN TERMINATION ===") - - self.stop_event.set() - - for provider in self.providers: - try: - if hasattr(provider, 'session'): - provider.session.close() - print(f"Closed HTTP session for provider: {provider.get_name()}") - except Exception as e: - print(f"Error closing session for {provider.get_name()}: {e}") - - if self.executor: - print("Shutting down executor with immediate cancellation...") - self.executor.shutdown(wait=False, cancel_futures=True) - - threading.Timer(2.0, self._force_stop_completion).start() - - print("Immediate termination requested - ongoing requests will be cancelled") - return True - print("No active scan to stop") - return False + if not self.scan_thread or not self.scan_thread.is_alive(): + print("No active scan thread to stop.") + # Cleanup state if inconsistent + if self.status == ScanStatus.RUNNING: + self.status = ScanStatus.STOPPED + return False + + print("=== INITIATING IMMEDIATE SCAN TERMINATION ===") + self.status = ScanStatus.STOPPED + self.stop_event.set() + + if self.executor: + print("Shutting down executor with immediate cancellation...") + self.executor.shutdown(wait=False, cancel_futures=True) + + print("Termination signal sent. The scan thread will stop shortly.") + return True except Exception as e: print(f"ERROR: Exception in stop_scan: {e}") traceback.print_exc() diff --git a/static/css/main.css b/static/css/main.css index 5accf53..483d8b8 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -314,9 +314,39 @@ input[type="text"]:focus, select:focus { .view-controls { display: flex; + gap: 1.5rem; + align-items: center; +} + +.filter-group { + display: flex; + align-items: center; gap: 0.5rem; } +.filter-group label { + font-size: 0.9rem; + color: #999; +} + +.filter-group select, +.filter-group input[type="range"] { + background-color: #1a1a1a; + border: 1px solid #555; + color: #c7c7c7; + padding: 0.25rem 0.5rem; +} + +.filter-group select { + max-width: 150px; +} + +#confidence-value { + min-width: 30px; + text-align: center; + color: #00ff41; +} + .graph-container { height: 800px; position: relative; @@ -905,4 +935,39 @@ input[type="text"]:focus, select:focus { transform: translateX(100%); opacity: 0; } +} + +/* dnsrecon/static/css/main.css */ + +/* ... (at the end of the file) */ + +.large-entity-nodes-list { + margin-top: 1rem; +} + +.large-entity-node-details { + margin-bottom: 0.5rem; + border: 1px solid #333; + border-radius: 3px; +} + +.large-entity-node-details summary { + padding: 0.5rem; + background-color: #3a3a3a; + cursor: pointer; + outline: none; +} + +.large-entity-node-details summary:hover { + background-color: #4a4a4a; +} + +.large-entity-node-details .detail-row { + margin-left: 1rem; + margin-right: 1rem; +} + +.large-entity-node-details .detail-section-header { + margin-left: 1rem; + margin-right: 1rem; } \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index d37f245..3146fe9 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -740,26 +740,21 @@ class DNSReconApp { this.elements.providerList.appendChild(providerItem); } } - + /** - * Show node details modal - * @param {string} nodeId - Node identifier - * @param {Object} node - Node data + * Generates the HTML for the node details view. + * @param {Object} node - The node object. + * @returns {string} The HTML string for the node details. */ - showNodeModal(nodeId, node) { - if (!this.elements.nodeModal) return; - - if (this.elements.modalTitle) { - this.elements.modalTitle.textContent = `Node Details`; - } - + generateNodeDetailsHtml(node) { + if(!node) return '