import os import time from typing import Optional from utils.display import Display from tui.help import HelpManager from core.session import Session from proto.c2_pb2 import Command from streams.udp_receiver import UDPReceiver from streams.config import ( UDP_HOST, UDP_PORT, IMAGE_DIR, MULTILAT_AUTH_TOKEN, WEB_HOST, WEB_PORT, DEFAULT_USERNAME, DEFAULT_PASSWORD, FLASK_SECRET_KEY ) from web.server import UnifiedWebServer class Commander: """Routes text commands to handlers. No UI, no readline.""" def __init__(self, session: Session): self.session = session self.help_manager = HelpManager(session.commands, dev_mode=True) def execute(self, cmd: str): """Execute a command string. Called by TUI on submit.""" if not cmd: return parts = cmd.split() action = parts[0] dispatch = { "help": lambda: self.help_manager.show(parts[1:]), "exit": lambda: Display.system_message("Use Ctrl+Q to quit"), "clear": lambda: Display.system_message("Use Ctrl+L to clear logs"), "list": lambda: self._handle_list(), "modules": lambda: self.help_manager.show_modules(), "group": lambda: self._handle_group(parts[1:]), "send": lambda: self._handle_send(parts), "active_commands": lambda: self._handle_active_commands(), "web": lambda: self._handle_web(parts[1:]), "camera": lambda: self._handle_camera(parts[1:]), "can": lambda: self._handle_can(parts[1:]), } handler = dispatch.get(action) if handler: handler() else: Display.error(f"Unknown command: {action}") # ================= HANDLERS ================= def _handle_list(self): now = time.time() active_devices = self.session.registry.all() if not active_devices: Display.system_message("No devices currently connected.") return Display.system_message("Connected Devices:") Display.system_message(f" {'ID':<18}{'IP Address':<18}{'Status':<18}{'Connected For':<18}{'Last Seen':<18}") Display.system_message(" " + "-" * 90) for d in active_devices: connected_for = Display.format_duration(now - d.connected_at) last_seen_duration = Display.format_duration(now - d.last_seen) Display.system_message(f" {d.id:<18}{d.address[0]:<18}{d.status:<18}{connected_for:<18}{last_seen_duration}") def _handle_send(self, parts): if len(parts) < 3: Display.error("Usage: send [args...]") return target_specifier = parts[1] command_parts = parts[2:] devices_to_target = [] target_description = "" if target_specifier == "all": devices_to_target = self.session.registry.all() target_description = "all connected devices" elif target_specifier == "group": if len(command_parts) < 2: Display.error("Usage: send group [args...]") return group_name = command_parts[0] command_parts = command_parts[1:] group_members_ids = self.session.groups.get(group_name) if not group_members_ids: Display.error(f"Group '{group_name}' not found or is empty.") return active_group_devices = [] for esp_id in group_members_ids: dev = self.session.registry.get(esp_id) if dev: active_group_devices.append(dev) else: Display.device_event(esp_id, f"Device in group '{group_name}' is not currently connected.") if not active_group_devices: Display.error(f"No active devices found in group '{group_name}'.") return devices_to_target = active_group_devices target_description = f"group '{group_name}' ({', '.join([d.id for d in devices_to_target])})" else: dev = self.session.registry.get(target_specifier) if dev: devices_to_target.append(dev) target_description = f"device '{target_specifier}'" else: Display.error(f"Device '{target_specifier}' not found.") return if not devices_to_target: Display.error("No target devices resolved for sending command.") return cmd_name = command_parts[0] argv = command_parts[1:] request_id_base = f"req-{int(time.time())}" Display.system_message(f"Sending command '{cmd_name}' to {target_description}...") for i, d in enumerate(devices_to_target): cmd = Command() cmd.device_id = d.id cmd.command_name = cmd_name cmd.argv.extend(argv) request_id = f"{request_id_base}-{i}" cmd.request_id = request_id Display.command_sent(d.id, cmd_name, request_id) self.session.transport.send_command(d.sock, cmd, d.id) self.session.active_commands[request_id] = { "device_id": d.id, "command_name": cmd_name, "start_time": time.time(), "status": "running", "output": [] } def _handle_group(self, parts): if not parts: Display.error("Usage: group ") return cmd = parts[0] if cmd == "add" and len(parts) >= 3: group = parts[1] added_devices = [] for esp_id in parts[2:]: if self.session.registry.get(esp_id): self.session.groups.add_device(group, esp_id) added_devices.append(esp_id) else: Display.device_event(esp_id, "Device not found, skipping group add.") if added_devices: Display.system_message(f"Group '{group}' updated. Added: {', '.join(added_devices)}") else: Display.system_message(f"No valid devices to add to group '{group}'.") elif cmd == "remove" and len(parts) >= 3: group = parts[1] removed_devices = [] for esp_id in parts[2:]: if esp_id in self.session.groups.get(group): self.session.groups.remove_device(group, esp_id) removed_devices.append(esp_id) else: Display.device_event(esp_id, f"Device not in group '{group}', skipping remove.") if removed_devices: Display.system_message(f"Group '{group}' updated. Removed: {', '.join(removed_devices)}") else: Display.system_message(f"No specified devices found in group '{group}' to remove.") elif cmd == "list": all_groups = self.session.groups.all_groups() if not all_groups: Display.system_message("No groups defined.") return Display.system_message("Defined Groups:") for g, members in all_groups.items(): Display.system_message(f" {g}: {', '.join(members) if members else 'No members'}") elif cmd == "show" and len(parts) == 2: group_name = parts[1] members = self.session.groups.get(group_name) if members: Display.system_message(f"Members of group '{group_name}': {', '.join(members)}") else: Display.system_message(f"Group '{group_name}' not found or empty.") else: Display.error("Invalid group command usage. See 'help group' for details.") def _handle_active_commands(self): if not self.session.active_commands: Display.system_message("No commands are currently active.") return Display.system_message("Active Commands:") Display.system_message(f" {'Request ID':<18}{'Device ID':<18}{'Command':<18}{'Status':<18}{'Elapsed Time':<18}") Display.system_message(" " + "-" * 90) now = time.time() for req_id, cmd_info in self.session.active_commands.items(): elapsed_time = Display.format_duration(now - cmd_info["start_time"]) Display.system_message( f" {req_id:<18}{cmd_info['device_id']:<18}" f"{cmd_info['command_name']:<18}{cmd_info['status']:<18}{elapsed_time}" ) def _handle_web(self, parts): """Handle web server commands.""" if not parts: Display.error("Usage: web ") return cmd = parts[0] if cmd == "start": if self.session.web_server and self.session.web_server.is_running: Display.system_message("Web server is already running.") return # Initialize honeypot dashboard components try: from hp_dashboard import HpStore, HpCommander, HpAlertEngine, HpGeoLookup _c3po_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _data_dir = os.path.join(_c3po_root, "data") os.makedirs(_data_dir, exist_ok=True) if not self.session.hp_store: self.session.hp_geo = HpGeoLookup( db_path=os.path.join(_data_dir, "honeypot_geo.db")) self.session.hp_store = HpStore( db_path=os.path.join(_data_dir, "honeypot_events.db"), geo_lookup=self.session.hp_geo) if not self.session.hp_alerts: self.session.hp_alerts = HpAlertEngine( db_path=os.path.join(_data_dir, "honeypot_alerts.db")) self.session.hp_alerts.set_store(self.session.hp_store) if not self.session.hp_commander: self.session.hp_commander = HpCommander( get_transport=lambda: self.session.transport, get_registry=lambda: self.session.registry, ) self.session.transport.hp_store = self.session.hp_store self.session.transport.hp_commander = self.session.hp_commander Display.system_message("Honeypot dashboard enabled (alerts + geo active)") except ImportError: Display.system_message("Honeypot dashboard not available (hp_dashboard not found)") self.session.web_server = UnifiedWebServer( host=WEB_HOST, port=WEB_PORT, image_dir=IMAGE_DIR, username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD, secret_key=FLASK_SECRET_KEY, device_registry=self.session.registry, transport=self.session.transport, session=self.session, mlat_engine=self.session.mlat_engine, multilat_token=MULTILAT_AUTH_TOKEN, camera_receiver=self.session.udp_receiver, hp_store=self.session.hp_store, hp_commander=self.session.hp_commander, hp_alerts=self.session.hp_alerts, hp_geo=self.session.hp_geo, ) if self.session.web_server.start(): Display.system_message(f"Web server started at {self.session.web_server.get_url()}") else: Display.error("Web server failed to start") elif cmd == "stop": if not self.session.web_server or not self.session.web_server.is_running: Display.system_message("Web server is not running.") return self.session.web_server.stop() Display.system_message("Web server stopped.") self.session.web_server = None elif cmd == "status": Display.system_message("Web Server Status:") if self.session.web_server and self.session.web_server.is_running: Display.system_message(f" Status: Running") Display.system_message(f" URL: {self.session.web_server.get_url()}") else: Display.system_message(f" Status: Stopped") Display.system_message("MLAT Engine:") state = self.session.mlat_engine.get_state() Display.system_message(f" Mode: {state.get('coord_mode', 'gps').upper()}") Display.system_message(f" Scanners: {state['scanners_count']}") if state['target']: pos = state['target']['position'] if 'lat' in pos: Display.system_message(f" Target: ({pos['lat']:.6f}, {pos['lon']:.6f})") else: Display.system_message(f" Target: ({pos['x']:.2f}m, {pos['y']:.2f}m)") else: Display.system_message(f" Target: Not calculated") else: Display.error("Invalid web command. Use: start, stop, status") def _handle_camera(self, parts): """Handle camera UDP receiver commands.""" if not parts: Display.error("Usage: camera ") return cmd = parts[0] if cmd == "start": if self.session.udp_receiver and self.session.udp_receiver.is_running: Display.system_message("Camera UDP receiver is already running.") return self.session.udp_receiver = UDPReceiver( host=UDP_HOST, port=UDP_PORT, image_dir=IMAGE_DIR, device_registry=self.session.registry ) if self.session.udp_receiver.start(): Display.system_message(f"Camera UDP receiver started on {UDP_HOST}:{UDP_PORT}") if self.session.web_server and self.session.web_server.is_running: self.session.web_server.set_camera_receiver(self.session.udp_receiver) Display.system_message("Web server updated with camera receiver") else: Display.error("Camera UDP receiver failed to start") elif cmd == "stop": if not self.session.udp_receiver or not self.session.udp_receiver.is_running: Display.system_message("Camera UDP receiver is not running.") return self.session.udp_receiver.stop() Display.system_message("Camera UDP receiver stopped.") self.session.udp_receiver = None if self.session.web_server and self.session.web_server.is_running: self.session.web_server.set_camera_receiver(None) elif cmd == "status": Display.system_message("Camera UDP Receiver Status:") if self.session.udp_receiver and self.session.udp_receiver.is_running: stats = self.session.udp_receiver.get_stats() Display.system_message(f" Status: Running on {UDP_HOST}:{UDP_PORT}") Display.system_message(f" Packets received: {stats['packets_received']}") Display.system_message(f" Frames decoded: {stats['frames_received']}") Display.system_message(f" Decode errors: {stats['decode_errors']}") Display.system_message(f" Invalid tokens: {stats['invalid_tokens']}") Display.system_message(f" Active cameras: {stats['active_cameras']}") else: Display.system_message(f" Status: Stopped") else: Display.error("Invalid camera command. Use: start, stop, status") def _handle_can(self, parts): """Handle CAN bus commands — show local CAN store stats or frames.""" if not parts: Display.error("Usage: can [device_id]") return cmd = parts[0] if cmd == "stats": device_id = parts[1] if len(parts) > 1 else None stats = self.session.can_store.get_stats(device_id=device_id) Display.system_message("CAN Bus Statistics:") Display.system_message(f" Total received: {stats['total_received']}") Display.system_message(f" Stored: {stats['total_stored']}") Display.system_message(f" Unique CAN IDs: {stats['unique_can_ids']}") if stats['can_ids']: Display.system_message(f" IDs: {', '.join(stats['can_ids'][:20])}") elif cmd == "frames": device_id = parts[1] if len(parts) > 1 else None limit = int(parts[2]) if len(parts) > 2 else 20 frames = self.session.can_store.get_frames(device_id=device_id, limit=limit) if not frames: Display.system_message("No CAN frames stored.") return Display.system_message(f"CAN Frames (last {len(frames)}):") Display.system_message(f" {'Device':<12}{'CAN ID':<10}{'DLC':<6}{'Data':<20}{'Timestamp'}") Display.system_message(" " + "-" * 70) for f in frames: Display.system_message( f" {f['device_id']:<12}{f['can_id']:<10}{f['dlc']:<6}" f"{f['data']:<20}{f['timestamp_ms']}" ) elif cmd == "clear": self.session.can_store.frames.clear() self.session.can_store.total_count = 0 Display.system_message("CAN frame store cleared.") else: Display.error("Invalid CAN command. Use: stats, frames, clear")