iotsploit-core 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iotsploit_core/__init__.py +7 -0
- iotsploit_core/context.py +27 -0
- iotsploit_core/core/__init__.py +0 -0
- iotsploit_core/core/base_plugin.py +290 -0
- iotsploit_core/core/device_config.py +105 -0
- iotsploit_core/core/device_manager.py +942 -0
- iotsploit_core/core/device_registry.py +118 -0
- iotsploit_core/core/device_scanner.py +74 -0
- iotsploit_core/core/device_spec.py +105 -0
- iotsploit_core/core/device_store.py +86 -0
- iotsploit_core/core/execution_backend.py +464 -0
- iotsploit_core/core/execution_queue.py +456 -0
- iotsploit_core/core/exploit_manager.py +728 -0
- iotsploit_core/core/exploit_spec.py +73 -0
- iotsploit_core/core/stream_manager.py +157 -0
- iotsploit_core/core/tool_config.py +430 -0
- iotsploit_core/core/tool_manager.py +1166 -0
- iotsploit_core/core/tool_service.py +936 -0
- iotsploit_core/domain/__init__.py +3 -0
- iotsploit_core/domain/device.py +70 -0
- iotsploit_core/domain/execution_plan.py +29 -0
- iotsploit_core/domain/plugin.py +17 -0
- iotsploit_core/domain/stream.py +63 -0
- iotsploit_core/domain/target.py +243 -0
- iotsploit_core/platforms/__init__.py +19 -0
- iotsploit_core/platforms/consts.py +25 -0
- iotsploit_core/ports/__init__.py +6 -0
- iotsploit_core/ports/driver_state_repo.py +15 -0
- iotsploit_core/ports/plugin_repo.py +26 -0
- iotsploit_core/ports/stream_backend.py +28 -0
- iotsploit_core/ports/task_runner.py +22 -0
- iotsploit_core/ports/wifi_backend.py +148 -0
- iotsploit_core/py.typed +2 -0
- iotsploit_core/utils/__init__.py +53 -0
- iotsploit_core/utils/exceptions.py +152 -0
- iotsploit_core/utils/helpers.py +65 -0
- iotsploit_core/utils/iots_logger.py +164 -0
- iotsploit_core/utils/result.py +41 -0
- iotsploit_core-0.0.6.dist-info/METADATA +117 -0
- iotsploit_core-0.0.6.dist-info/RECORD +41 -0
- iotsploit_core-0.0.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Tool management: discovery, validation, and cross-platform execution."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional, Union, Any, Tuple
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import Enum
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
|
|
20
|
+
# Import ExecutionResult from execution_backend to avoid duplication
|
|
21
|
+
from .execution_backend import ExecutionResult, get_execution_backend_manager
|
|
22
|
+
from .tool_config import get_tool_config_manager, ToolConfig
|
|
23
|
+
from .execution_queue import get_execution_queue, TaskPriority
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
class ToolStatus(Enum):
|
|
28
|
+
"""Tool availability status"""
|
|
29
|
+
AVAILABLE = "available"
|
|
30
|
+
MISSING = "missing"
|
|
31
|
+
INVALID = "invalid"
|
|
32
|
+
PERMISSION_DENIED = "permission_denied"
|
|
33
|
+
VERSION_MISMATCH = "version_mismatch"
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ToolInfo:
|
|
37
|
+
"""Information about a tool"""
|
|
38
|
+
name: str
|
|
39
|
+
path: Optional[str] = None
|
|
40
|
+
version: Optional[str] = None
|
|
41
|
+
status: ToolStatus = ToolStatus.MISSING
|
|
42
|
+
aliases: List[str] = field(default_factory=list)
|
|
43
|
+
dependencies: List[str] = field(default_factory=list)
|
|
44
|
+
platforms: List[str] = field(default_factory=lambda: ["linux", "darwin", "windows"])
|
|
45
|
+
min_version: Optional[str] = None
|
|
46
|
+
max_version: Optional[str] = None
|
|
47
|
+
last_checked: Optional[float] = None
|
|
48
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class CategoryInfo:
|
|
52
|
+
"""Information about the tool category"""
|
|
53
|
+
name: str
|
|
54
|
+
description: str
|
|
55
|
+
tools: List[str] = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
class SystemHealth(Enum):
|
|
58
|
+
"""System health status"""
|
|
59
|
+
HEALTHY = "healthy"
|
|
60
|
+
DEGRADED = "degraded"
|
|
61
|
+
CRITICAL = "critical"
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class SystemStatus:
|
|
65
|
+
"""Overall system status"""
|
|
66
|
+
health: SystemHealth
|
|
67
|
+
total_tools: int
|
|
68
|
+
available_tools: int
|
|
69
|
+
missing_tools: int
|
|
70
|
+
required_missing: int
|
|
71
|
+
can_operate: bool
|
|
72
|
+
recommendations: List[str] = field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
class ToolRegistry:
|
|
75
|
+
"""Central registry for managing tool information"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
78
|
+
self.tools: Dict[str, ToolInfo] = {}
|
|
79
|
+
self.config_path = config_path or self._get_default_config_path()
|
|
80
|
+
self._lock = threading.RLock()
|
|
81
|
+
self._load_config()
|
|
82
|
+
|
|
83
|
+
def _get_default_config_path(self) -> str:
|
|
84
|
+
"""Get default configuration file path, preferring project-specific config"""
|
|
85
|
+
# First try project-specific configuration
|
|
86
|
+
project_config = Path('conf') / 'tools.json'
|
|
87
|
+
if project_config.exists():
|
|
88
|
+
return str(project_config)
|
|
89
|
+
|
|
90
|
+
# Fall back to user-specific configuration
|
|
91
|
+
config_dir = Path.home() / ".iotsploit"
|
|
92
|
+
config_dir.mkdir(exist_ok=True)
|
|
93
|
+
return str(config_dir / "tools.json")
|
|
94
|
+
|
|
95
|
+
def _load_config(self):
|
|
96
|
+
"""Load tool configuration from file"""
|
|
97
|
+
if os.path.exists(self.config_path):
|
|
98
|
+
try:
|
|
99
|
+
with open(self.config_path, 'r') as f:
|
|
100
|
+
config = json.load(f)
|
|
101
|
+
for tool_name, tool_data in config.get('tools', {}).items():
|
|
102
|
+
# Convert status string back to enum if needed
|
|
103
|
+
if 'status' in tool_data and isinstance(tool_data['status'], str):
|
|
104
|
+
try:
|
|
105
|
+
tool_data['status'] = ToolStatus(tool_data['status'])
|
|
106
|
+
except ValueError:
|
|
107
|
+
# If status string is invalid, default to MISSING
|
|
108
|
+
tool_data['status'] = ToolStatus.MISSING
|
|
109
|
+
self.tools[tool_name] = ToolInfo(**tool_data)
|
|
110
|
+
logger.info(f"Loaded {len(self.tools)} tools from config")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Failed to load tool config: {e}")
|
|
113
|
+
else:
|
|
114
|
+
self._create_default_config()
|
|
115
|
+
|
|
116
|
+
def _create_default_config(self):
|
|
117
|
+
"""Create default tool configuration"""
|
|
118
|
+
default_tools = {
|
|
119
|
+
'nmap': ToolInfo(
|
|
120
|
+
name='nmap',
|
|
121
|
+
aliases=['nmap'],
|
|
122
|
+
platforms=['linux', 'darwin', 'windows'],
|
|
123
|
+
min_version='7.0'
|
|
124
|
+
),
|
|
125
|
+
'adb': ToolInfo(
|
|
126
|
+
name='adb',
|
|
127
|
+
aliases=['adb'],
|
|
128
|
+
platforms=['linux', 'darwin', 'windows']
|
|
129
|
+
),
|
|
130
|
+
'openssl': ToolInfo(
|
|
131
|
+
name='openssl',
|
|
132
|
+
aliases=['openssl'],
|
|
133
|
+
platforms=['linux', 'darwin', 'windows']
|
|
134
|
+
),
|
|
135
|
+
'binwalk': ToolInfo(
|
|
136
|
+
name='binwalk',
|
|
137
|
+
aliases=['binwalk'],
|
|
138
|
+
platforms=['linux', 'darwin']
|
|
139
|
+
),
|
|
140
|
+
'john': ToolInfo(
|
|
141
|
+
name='john',
|
|
142
|
+
aliases=['john', 'john-the-ripper'],
|
|
143
|
+
platforms=['linux', 'darwin', 'windows']
|
|
144
|
+
),
|
|
145
|
+
'hashcat': ToolInfo(
|
|
146
|
+
name='hashcat',
|
|
147
|
+
aliases=['hashcat'],
|
|
148
|
+
platforms=['linux', 'darwin', 'windows']
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for name, tool_info in default_tools.items():
|
|
153
|
+
self.tools[name] = tool_info
|
|
154
|
+
|
|
155
|
+
self.save_config()
|
|
156
|
+
|
|
157
|
+
def save_config(self):
|
|
158
|
+
"""Save tool configuration to file"""
|
|
159
|
+
try:
|
|
160
|
+
config = {
|
|
161
|
+
'tools': {
|
|
162
|
+
name: {
|
|
163
|
+
'name': tool.name,
|
|
164
|
+
'path': tool.path,
|
|
165
|
+
'version': tool.version,
|
|
166
|
+
'status': tool.status.value if hasattr(tool.status, 'value') else str(tool.status),
|
|
167
|
+
'aliases': tool.aliases,
|
|
168
|
+
'dependencies': tool.dependencies,
|
|
169
|
+
'platforms': tool.platforms,
|
|
170
|
+
'min_version': tool.min_version,
|
|
171
|
+
'max_version': tool.max_version,
|
|
172
|
+
'last_checked': tool.last_checked,
|
|
173
|
+
'metadata': tool.metadata
|
|
174
|
+
}
|
|
175
|
+
for name, tool in self.tools.items()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
with open(self.config_path, 'w') as f:
|
|
180
|
+
json.dump(config, f, indent=2)
|
|
181
|
+
logger.debug(f"Saved tool config to {self.config_path}")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Failed to save tool config: {e}")
|
|
184
|
+
# Add more detailed error information
|
|
185
|
+
import traceback
|
|
186
|
+
logger.debug(f"Save config traceback: {traceback.format_exc()}")
|
|
187
|
+
|
|
188
|
+
def register_tool(self, tool_info: ToolInfo, save_config: bool = True):
|
|
189
|
+
"""Register a new tool"""
|
|
190
|
+
with self._lock:
|
|
191
|
+
self.tools[tool_info.name] = tool_info
|
|
192
|
+
if save_config:
|
|
193
|
+
self.save_config()
|
|
194
|
+
|
|
195
|
+
def get_tool(self, name: str) -> Optional[ToolInfo]:
|
|
196
|
+
"""Get tool information by name or alias"""
|
|
197
|
+
with self._lock:
|
|
198
|
+
# Direct name match
|
|
199
|
+
if name in self.tools:
|
|
200
|
+
return self.tools[name]
|
|
201
|
+
|
|
202
|
+
# Alias match
|
|
203
|
+
for tool in self.tools.values():
|
|
204
|
+
if name in tool.aliases:
|
|
205
|
+
return tool
|
|
206
|
+
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
def list_tools(self) -> List[ToolInfo]:
|
|
210
|
+
"""List all registered tools"""
|
|
211
|
+
with self._lock:
|
|
212
|
+
return list(self.tools.values())
|
|
213
|
+
|
|
214
|
+
class PathResolver:
|
|
215
|
+
"""Resolves tool paths across different platforms"""
|
|
216
|
+
|
|
217
|
+
def __init__(self):
|
|
218
|
+
self.platform = platform.system().lower()
|
|
219
|
+
self.search_paths = self._get_search_paths()
|
|
220
|
+
self._path_cache: Dict[str, str] = {}
|
|
221
|
+
|
|
222
|
+
def _get_search_paths(self) -> List[str]:
|
|
223
|
+
"""Get platform-specific search paths"""
|
|
224
|
+
paths = []
|
|
225
|
+
|
|
226
|
+
# System PATH
|
|
227
|
+
system_path = os.environ.get('PATH', '')
|
|
228
|
+
if system_path:
|
|
229
|
+
paths.extend(system_path.split(os.pathsep))
|
|
230
|
+
|
|
231
|
+
# Platform-specific paths
|
|
232
|
+
if self.platform == 'linux':
|
|
233
|
+
paths.extend([
|
|
234
|
+
'/usr/bin', '/usr/local/bin', '/bin', '/sbin',
|
|
235
|
+
'/usr/sbin', '/usr/local/sbin', '/opt/bin'
|
|
236
|
+
])
|
|
237
|
+
elif self.platform == 'darwin': # macOS
|
|
238
|
+
paths.extend([
|
|
239
|
+
'/usr/bin', '/usr/local/bin', '/bin', '/sbin',
|
|
240
|
+
'/usr/sbin', '/opt/homebrew/bin', '/usr/local/sbin'
|
|
241
|
+
])
|
|
242
|
+
elif self.platform == 'windows':
|
|
243
|
+
paths.extend([
|
|
244
|
+
'C:\\Windows\\System32', 'C:\\Windows',
|
|
245
|
+
'C:\\Program Files', 'C:\\Program Files (x86)'
|
|
246
|
+
])
|
|
247
|
+
|
|
248
|
+
# User-specific paths
|
|
249
|
+
home = Path.home()
|
|
250
|
+
paths.extend([
|
|
251
|
+
str(home / 'bin'),
|
|
252
|
+
str(home / '.local' / 'bin'),
|
|
253
|
+
str(home / 'go' / 'bin')
|
|
254
|
+
])
|
|
255
|
+
|
|
256
|
+
return [p for p in paths if p and os.path.exists(p)]
|
|
257
|
+
|
|
258
|
+
def resolve_tool_path(self, tool_name: str, aliases: List[str] = None) -> Optional[str]:
|
|
259
|
+
"""Resolve the full path to a tool"""
|
|
260
|
+
# Check cache first
|
|
261
|
+
cache_key = f"{tool_name}:{','.join(aliases or [])}"
|
|
262
|
+
if cache_key in self._path_cache:
|
|
263
|
+
cached_path = self._path_cache[cache_key]
|
|
264
|
+
if os.path.exists(cached_path):
|
|
265
|
+
return cached_path
|
|
266
|
+
else:
|
|
267
|
+
# Remove invalid cache entry
|
|
268
|
+
del self._path_cache[cache_key]
|
|
269
|
+
|
|
270
|
+
# Try tool name and aliases
|
|
271
|
+
names_to_try = [tool_name] + (aliases or [])
|
|
272
|
+
|
|
273
|
+
for name in names_to_try:
|
|
274
|
+
# Try shutil.which first (respects PATH)
|
|
275
|
+
path = shutil.which(name)
|
|
276
|
+
if path and self._validate_executable(path):
|
|
277
|
+
self._path_cache[cache_key] = path
|
|
278
|
+
return path
|
|
279
|
+
|
|
280
|
+
# Manual search in known paths
|
|
281
|
+
for search_path in self.search_paths:
|
|
282
|
+
potential_path = os.path.join(search_path, name)
|
|
283
|
+
|
|
284
|
+
# Add .exe extension on Windows
|
|
285
|
+
if self.platform == 'windows' and not potential_path.endswith('.exe'):
|
|
286
|
+
potential_path += '.exe'
|
|
287
|
+
|
|
288
|
+
if os.path.exists(potential_path) and self._validate_executable(potential_path):
|
|
289
|
+
self._path_cache[cache_key] = potential_path
|
|
290
|
+
return potential_path
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def _validate_executable(self, path: str) -> bool:
|
|
295
|
+
"""Validate that a path points to an executable file"""
|
|
296
|
+
try:
|
|
297
|
+
return os.path.isfile(path) and os.access(path, os.X_OK)
|
|
298
|
+
except (OSError, PermissionError):
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
class ToolValidator:
|
|
302
|
+
"""Validates tool availability and versions"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, path_resolver: PathResolver):
|
|
305
|
+
self.path_resolver = path_resolver
|
|
306
|
+
|
|
307
|
+
def validate_tool(self, tool_info: ToolInfo) -> ToolInfo:
|
|
308
|
+
"""Validate a tool and update its status"""
|
|
309
|
+
# Check if tool is supported on current platform
|
|
310
|
+
current_platform = platform.system().lower()
|
|
311
|
+
if current_platform not in tool_info.platforms:
|
|
312
|
+
tool_info.status = ToolStatus.INVALID
|
|
313
|
+
return tool_info
|
|
314
|
+
|
|
315
|
+
# Resolve tool path
|
|
316
|
+
if not tool_info.path:
|
|
317
|
+
tool_info.path = self.path_resolver.resolve_tool_path(
|
|
318
|
+
tool_info.name, tool_info.aliases
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if not tool_info.path:
|
|
322
|
+
tool_info.status = ToolStatus.MISSING
|
|
323
|
+
return tool_info
|
|
324
|
+
|
|
325
|
+
# Validate executable permissions
|
|
326
|
+
if not os.access(tool_info.path, os.X_OK):
|
|
327
|
+
tool_info.status = ToolStatus.PERMISSION_DENIED
|
|
328
|
+
return tool_info
|
|
329
|
+
|
|
330
|
+
# Get and validate version
|
|
331
|
+
try:
|
|
332
|
+
version = self._get_tool_version(tool_info)
|
|
333
|
+
if version:
|
|
334
|
+
tool_info.version = version
|
|
335
|
+
if self._check_version_compatibility(tool_info):
|
|
336
|
+
tool_info.status = ToolStatus.AVAILABLE
|
|
337
|
+
else:
|
|
338
|
+
tool_info.status = ToolStatus.VERSION_MISMATCH
|
|
339
|
+
else:
|
|
340
|
+
tool_info.status = ToolStatus.AVAILABLE # Version check failed but tool exists
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.warning(f"Version check failed for {tool_info.name}: {e}")
|
|
343
|
+
tool_info.status = ToolStatus.AVAILABLE # Assume available if version check fails
|
|
344
|
+
|
|
345
|
+
tool_info.last_checked = time.time()
|
|
346
|
+
return tool_info
|
|
347
|
+
|
|
348
|
+
def _get_tool_version(self, tool_info: ToolInfo) -> Optional[str]:
|
|
349
|
+
"""Get tool version"""
|
|
350
|
+
version_commands = [
|
|
351
|
+
['--version'],
|
|
352
|
+
['-v'],
|
|
353
|
+
['-V'],
|
|
354
|
+
['version'],
|
|
355
|
+
['--help'] # Some tools only show version in help
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
for cmd_args in version_commands:
|
|
359
|
+
try:
|
|
360
|
+
result = subprocess.run(
|
|
361
|
+
[tool_info.path] + cmd_args,
|
|
362
|
+
capture_output=True,
|
|
363
|
+
text=True,
|
|
364
|
+
timeout=10
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Look for version in stdout or stderr
|
|
368
|
+
output = result.stdout + result.stderr
|
|
369
|
+
version = self._extract_version(output)
|
|
370
|
+
if version:
|
|
371
|
+
return version
|
|
372
|
+
|
|
373
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def _extract_version(self, output: str) -> Optional[str]:
|
|
379
|
+
"""Extract version number from command output"""
|
|
380
|
+
import re
|
|
381
|
+
|
|
382
|
+
# Common version patterns
|
|
383
|
+
patterns = [
|
|
384
|
+
r'version\s+(\d+\.\d+(?:\.\d+)?)',
|
|
385
|
+
r'v(\d+\.\d+(?:\.\d+)?)',
|
|
386
|
+
r'(\d+\.\d+(?:\.\d+)?)',
|
|
387
|
+
r'Version:\s*(\d+\.\d+(?:\.\d+)?)'
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
for pattern in patterns:
|
|
391
|
+
match = re.search(pattern, output, re.IGNORECASE)
|
|
392
|
+
if match:
|
|
393
|
+
return match.group(1)
|
|
394
|
+
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
def _check_version_compatibility(self, tool_info: ToolInfo) -> bool:
|
|
398
|
+
"""Check if tool version meets requirements"""
|
|
399
|
+
if not tool_info.version:
|
|
400
|
+
return True # No version info, assume compatible
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
from packaging import version
|
|
404
|
+
tool_version = version.parse(tool_info.version)
|
|
405
|
+
|
|
406
|
+
if tool_info.min_version:
|
|
407
|
+
min_ver = version.parse(tool_info.min_version)
|
|
408
|
+
if tool_version < min_ver:
|
|
409
|
+
return False
|
|
410
|
+
|
|
411
|
+
if tool_info.max_version:
|
|
412
|
+
max_ver = version.parse(tool_info.max_version)
|
|
413
|
+
if tool_version > max_ver:
|
|
414
|
+
return False
|
|
415
|
+
|
|
416
|
+
return True
|
|
417
|
+
except Exception:
|
|
418
|
+
# If version parsing fails, assume compatible
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
class ToolExecutor:
|
|
422
|
+
"""Executes tools with proper environment and error handling"""
|
|
423
|
+
|
|
424
|
+
def __init__(self, tool_registry: ToolRegistry, validator: ToolValidator):
|
|
425
|
+
self.registry = tool_registry
|
|
426
|
+
self.validator = validator
|
|
427
|
+
self.backend_manager = get_execution_backend_manager()
|
|
428
|
+
|
|
429
|
+
def execute_tool(self, tool_name: str, args: List[str],
|
|
430
|
+
working_dir: Optional[str] = None,
|
|
431
|
+
env: Optional[Dict[str, str]] = None,
|
|
432
|
+
timeout: Optional[int] = None,
|
|
433
|
+
backend: Optional[str] = None) -> ExecutionResult:
|
|
434
|
+
"""Execute a tool with given arguments"""
|
|
435
|
+
|
|
436
|
+
# Get tool info
|
|
437
|
+
tool_info = self.registry.get_tool(tool_name)
|
|
438
|
+
if not tool_info:
|
|
439
|
+
raise ValueError(f"Tool '{tool_name}' not found in registry")
|
|
440
|
+
|
|
441
|
+
# Validate tool
|
|
442
|
+
tool_info = self.validator.validate_tool(tool_info)
|
|
443
|
+
if tool_info.status != ToolStatus.AVAILABLE:
|
|
444
|
+
raise RuntimeError(f"Tool '{tool_name}' is not available: {tool_info.status.value}")
|
|
445
|
+
|
|
446
|
+
logger.info(f"Executing: {tool_info.path} {' '.join(args)}")
|
|
447
|
+
|
|
448
|
+
# Execute using backend manager
|
|
449
|
+
return self.backend_manager.execute(
|
|
450
|
+
tool_path=tool_info.path,
|
|
451
|
+
args=args,
|
|
452
|
+
backend=backend,
|
|
453
|
+
working_dir=working_dir,
|
|
454
|
+
env=env,
|
|
455
|
+
timeout=timeout
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
class ToolManager:
|
|
459
|
+
"""Main tool management interface"""
|
|
460
|
+
|
|
461
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
462
|
+
self.registry = ToolRegistry(config_path)
|
|
463
|
+
self.path_resolver = PathResolver()
|
|
464
|
+
self.validator = ToolValidator(self.path_resolver)
|
|
465
|
+
self.executor = ToolExecutor(self.registry, self.validator)
|
|
466
|
+
self.config_manager = get_tool_config_manager()
|
|
467
|
+
self.backend_manager = get_execution_backend_manager()
|
|
468
|
+
self.queue_manager = get_execution_queue()
|
|
469
|
+
# Initialize tools from JSON configuration
|
|
470
|
+
self._initialize_tools()
|
|
471
|
+
|
|
472
|
+
def discover_tools(self) -> Dict[str, ToolStatus]:
|
|
473
|
+
"""Discover and validate all registered tools"""
|
|
474
|
+
results = {}
|
|
475
|
+
|
|
476
|
+
for tool_name, tool_info in self.registry.tools.items():
|
|
477
|
+
validated_tool = self.validator.validate_tool(tool_info)
|
|
478
|
+
self.registry.tools[tool_name] = validated_tool
|
|
479
|
+
results[tool_name] = validated_tool.status
|
|
480
|
+
|
|
481
|
+
self.registry.save_config()
|
|
482
|
+
return results
|
|
483
|
+
|
|
484
|
+
def get_tool_status(self, tool_name: str) -> Optional[ToolStatus]:
|
|
485
|
+
"""Get the status of a specific tool"""
|
|
486
|
+
tool_info = self.registry.get_tool(tool_name)
|
|
487
|
+
if not tool_info:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
# Refresh status if not checked recently
|
|
491
|
+
if not tool_info.last_checked or (time.time() - tool_info.last_checked) > 3600:
|
|
492
|
+
tool_info = self.validator.validate_tool(tool_info)
|
|
493
|
+
self.registry.tools[tool_info.name] = tool_info
|
|
494
|
+
self.registry.save_config()
|
|
495
|
+
|
|
496
|
+
return tool_info.status
|
|
497
|
+
|
|
498
|
+
def execute(self, tool_name: str, args: List[str], **kwargs) -> ExecutionResult:
|
|
499
|
+
"""Execute a tool with arguments"""
|
|
500
|
+
return self.executor.execute_tool(tool_name, args, **kwargs)
|
|
501
|
+
|
|
502
|
+
def is_available(self, tool_name: str) -> bool:
|
|
503
|
+
"""Check if a tool is available for use"""
|
|
504
|
+
status = self.get_tool_status(tool_name)
|
|
505
|
+
return status == ToolStatus.AVAILABLE
|
|
506
|
+
|
|
507
|
+
def list_available_tools(self) -> List[str]:
|
|
508
|
+
"""List all available tools"""
|
|
509
|
+
available = []
|
|
510
|
+
for tool_name in self.registry.tools.keys():
|
|
511
|
+
if self.is_available(tool_name):
|
|
512
|
+
available.append(tool_name)
|
|
513
|
+
return available
|
|
514
|
+
|
|
515
|
+
def add_tool(self, name: str, path: Optional[str] = None,
|
|
516
|
+
aliases: List[str] = None, save_config: bool = True, **kwargs) -> bool:
|
|
517
|
+
"""Add a new tool to the registry"""
|
|
518
|
+
tool_info = ToolInfo(
|
|
519
|
+
name=name,
|
|
520
|
+
path=path,
|
|
521
|
+
aliases=aliases or [],
|
|
522
|
+
**kwargs
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# Validate the tool
|
|
526
|
+
validated_tool = self.validator.validate_tool(tool_info)
|
|
527
|
+
self.registry.register_tool(validated_tool, save_config=save_config)
|
|
528
|
+
|
|
529
|
+
return validated_tool.status == ToolStatus.AVAILABLE
|
|
530
|
+
|
|
531
|
+
# ========== Methods from ToolCategoryManager ==========
|
|
532
|
+
|
|
533
|
+
def _initialize_tools(self):
|
|
534
|
+
"""Initialize all tools from JSON configuration"""
|
|
535
|
+
logger.info("Initializing tools from JSON config")
|
|
536
|
+
|
|
537
|
+
# Get tools from the main tools.json file
|
|
538
|
+
tool_configs = self.config_manager.get_tools_by_category("tools")
|
|
539
|
+
|
|
540
|
+
# If no tools found in "tools" category, try loading from the main config
|
|
541
|
+
if not tool_configs:
|
|
542
|
+
# Try to load from tools.json in conf directory first, then fallback
|
|
543
|
+
tools_file = Path('conf') / 'tools.json'
|
|
544
|
+
if not tools_file.exists():
|
|
545
|
+
tools_file = self.config_manager.config_dir / "tools.json"
|
|
546
|
+
|
|
547
|
+
if tools_file.exists():
|
|
548
|
+
with open(tools_file, 'r', encoding='utf-8') as f:
|
|
549
|
+
tools_data = json.load(f)
|
|
550
|
+
|
|
551
|
+
tools_added = 0
|
|
552
|
+
# Support tools field as dict or list
|
|
553
|
+
tools_section = tools_data.get('tools', [])
|
|
554
|
+
if isinstance(tools_section, dict):
|
|
555
|
+
items = tools_section.items()
|
|
556
|
+
else:
|
|
557
|
+
items = [(td.get('name', 'unknown'), td) for td in tools_section if isinstance(td, dict)]
|
|
558
|
+
for tool_name, tool_data in items:
|
|
559
|
+
try:
|
|
560
|
+
# Build tool_data dict and ensure it contains name
|
|
561
|
+
data = tool_data.copy() if isinstance(tool_data, dict) else {}
|
|
562
|
+
data.setdefault('name', tool_name)
|
|
563
|
+
tool_config = ToolConfig.from_dict(data)
|
|
564
|
+
tool_config.category = "tools"
|
|
565
|
+
# Add tool to manager (don't save config for each tool)
|
|
566
|
+
success = self.add_tool(
|
|
567
|
+
name=tool_config.name,
|
|
568
|
+
aliases=tool_config.aliases,
|
|
569
|
+
min_version=tool_config.min_version,
|
|
570
|
+
platforms=tool_config.platforms,
|
|
571
|
+
path=tool_config.path,
|
|
572
|
+
save_config=False
|
|
573
|
+
)
|
|
574
|
+
if success:
|
|
575
|
+
tools_added += 1
|
|
576
|
+
logger.debug(f"Added tool: {tool_config.name}")
|
|
577
|
+
else:
|
|
578
|
+
logger.warning(f"Failed to add tool: {tool_config.name}")
|
|
579
|
+
except Exception as e:
|
|
580
|
+
logger.error(f"Error adding tool {tool_name}: {e}")
|
|
581
|
+
|
|
582
|
+
# Save config once at the end
|
|
583
|
+
if tools_added > 0:
|
|
584
|
+
self.registry.save_config()
|
|
585
|
+
logger.info(f"Successfully initialized {tools_added} tools")
|
|
586
|
+
else:
|
|
587
|
+
# Load tools from category
|
|
588
|
+
tools_added = 0
|
|
589
|
+
for tool_config in tool_configs:
|
|
590
|
+
try:
|
|
591
|
+
success = self.add_tool(
|
|
592
|
+
name=tool_config.name,
|
|
593
|
+
aliases=tool_config.aliases,
|
|
594
|
+
min_version=tool_config.min_version,
|
|
595
|
+
platforms=tool_config.platforms,
|
|
596
|
+
path=tool_config.path,
|
|
597
|
+
save_config=False # Don't save for each tool
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if success:
|
|
601
|
+
tools_added += 1
|
|
602
|
+
logger.debug(f"Added tool: {tool_config.name}")
|
|
603
|
+
else:
|
|
604
|
+
logger.warning(f"Failed to add tool: {tool_config.name}")
|
|
605
|
+
|
|
606
|
+
except Exception as e:
|
|
607
|
+
logger.error(f"Error adding tool {tool_config.name}: {e}")
|
|
608
|
+
|
|
609
|
+
# Save config once at the end
|
|
610
|
+
if tools_added > 0:
|
|
611
|
+
self.registry.save_config()
|
|
612
|
+
logger.info(f"Successfully initialized {tools_added} tools")
|
|
613
|
+
|
|
614
|
+
def get_category_info(self) -> CategoryInfo:
|
|
615
|
+
"""Get category information from JSON config"""
|
|
616
|
+
# Try to get from tools.json in conf directory first, then fallback
|
|
617
|
+
tools_file = Path('conf') / 'tools.json'
|
|
618
|
+
if not tools_file.exists():
|
|
619
|
+
tools_file = self.config_manager.config_dir / "tools.json"
|
|
620
|
+
|
|
621
|
+
if tools_file.exists():
|
|
622
|
+
try:
|
|
623
|
+
with open(tools_file, 'r', encoding='utf-8') as f:
|
|
624
|
+
tools_data = json.load(f)
|
|
625
|
+
|
|
626
|
+
# Handle tools field, support dict or list format
|
|
627
|
+
tools_section = tools_data.get('tools', [])
|
|
628
|
+
all_tools = []
|
|
629
|
+
|
|
630
|
+
if isinstance(tools_section, dict):
|
|
631
|
+
# tools field is dict format: {"tool_name": {...}, ...}
|
|
632
|
+
all_tools = list(tools_section.keys())
|
|
633
|
+
elif isinstance(tools_section, list):
|
|
634
|
+
# tools field is list format: [{"name": "tool_name", ...}, ...]
|
|
635
|
+
all_tools = [tool.get('name', 'unknown') for tool in tools_section if isinstance(tool, dict)]
|
|
636
|
+
|
|
637
|
+
return CategoryInfo(
|
|
638
|
+
name=tools_data.get('name', 'IoTSploit Tools'),
|
|
639
|
+
description=tools_data.get('description', 'All tools for IoTSploit'),
|
|
640
|
+
tools=all_tools
|
|
641
|
+
)
|
|
642
|
+
except Exception as e:
|
|
643
|
+
logger.error(f"Error reading tools.json: {e}")
|
|
644
|
+
|
|
645
|
+
# Fallback
|
|
646
|
+
return CategoryInfo(
|
|
647
|
+
name="IoTSploit Tools",
|
|
648
|
+
description="All tools for IoTSploit security testing",
|
|
649
|
+
tools=[]
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
def get_available_tools(self) -> List[str]:
|
|
653
|
+
"""Get list of available tools"""
|
|
654
|
+
category_info = self.get_category_info()
|
|
655
|
+
available = []
|
|
656
|
+
|
|
657
|
+
for tool_name in category_info.tools:
|
|
658
|
+
if self.is_available(tool_name):
|
|
659
|
+
available.append(tool_name)
|
|
660
|
+
|
|
661
|
+
return available
|
|
662
|
+
|
|
663
|
+
def get_missing_tools(self) -> List[str]:
|
|
664
|
+
"""Get list of missing tools"""
|
|
665
|
+
category_info = self.get_category_info()
|
|
666
|
+
missing = []
|
|
667
|
+
|
|
668
|
+
for tool_name in category_info.tools:
|
|
669
|
+
if not self.is_available(tool_name):
|
|
670
|
+
missing.append(tool_name)
|
|
671
|
+
|
|
672
|
+
return missing
|
|
673
|
+
|
|
674
|
+
def get_required_tools(self) -> List[str]:
|
|
675
|
+
"""Get list of required tools"""
|
|
676
|
+
category_info = self.get_category_info()
|
|
677
|
+
required = []
|
|
678
|
+
|
|
679
|
+
for tool_name in category_info.tools:
|
|
680
|
+
tool_config = self.get_tool_config(tool_name)
|
|
681
|
+
if tool_config and tool_config.required:
|
|
682
|
+
required.append(tool_name)
|
|
683
|
+
|
|
684
|
+
return required
|
|
685
|
+
|
|
686
|
+
def get_optional_tools(self) -> List[str]:
|
|
687
|
+
"""Get list of optional tools"""
|
|
688
|
+
category_info = self.get_category_info()
|
|
689
|
+
optional = []
|
|
690
|
+
|
|
691
|
+
for tool_name in category_info.tools:
|
|
692
|
+
tool_config = self.get_tool_config(tool_name)
|
|
693
|
+
if tool_config and not tool_config.required:
|
|
694
|
+
optional.append(tool_name)
|
|
695
|
+
|
|
696
|
+
return optional
|
|
697
|
+
|
|
698
|
+
def validate_tools(self) -> Dict[str, Any]:
|
|
699
|
+
"""Validate all tools"""
|
|
700
|
+
category_info = self.get_category_info()
|
|
701
|
+
required_tools = self.get_required_tools()
|
|
702
|
+
|
|
703
|
+
results = {
|
|
704
|
+
'total_tools': len(category_info.tools),
|
|
705
|
+
'available_tools': [],
|
|
706
|
+
'missing_tools': [],
|
|
707
|
+
'required_missing': [],
|
|
708
|
+
'can_operate': True
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
for tool_name in category_info.tools:
|
|
712
|
+
if self.is_available(tool_name):
|
|
713
|
+
results['available_tools'].append(tool_name)
|
|
714
|
+
else:
|
|
715
|
+
results['missing_tools'].append(tool_name)
|
|
716
|
+
if tool_name in required_tools:
|
|
717
|
+
results['required_missing'].append(tool_name)
|
|
718
|
+
|
|
719
|
+
# Check if we can operate
|
|
720
|
+
results['can_operate'] = len(results['required_missing']) == 0
|
|
721
|
+
|
|
722
|
+
return results
|
|
723
|
+
|
|
724
|
+
def get_tool_config(self, tool_name: str) -> Optional[ToolConfig]:
|
|
725
|
+
"""Get tool configuration from JSON"""
|
|
726
|
+
return self.config_manager.get_tool_config(tool_name)
|
|
727
|
+
|
|
728
|
+
def get_install_hints(self) -> Dict[str, str]:
|
|
729
|
+
"""Get installation hints for missing tools"""
|
|
730
|
+
missing_tools = self.get_missing_tools()
|
|
731
|
+
hints = {}
|
|
732
|
+
|
|
733
|
+
for tool_name in missing_tools:
|
|
734
|
+
tool_config = self.get_tool_config(tool_name)
|
|
735
|
+
if tool_config and tool_config.install_hint:
|
|
736
|
+
hints[tool_name] = tool_config.install_hint
|
|
737
|
+
|
|
738
|
+
return hints
|
|
739
|
+
|
|
740
|
+
def reload_configurations(self):
|
|
741
|
+
"""Reload all configurations from JSON files"""
|
|
742
|
+
self.config_manager.reload_configs()
|
|
743
|
+
self._initialize_tools()
|
|
744
|
+
logger.info("Reloaded all tool configurations from JSON files")
|
|
745
|
+
|
|
746
|
+
def get_config_stats(self) -> Dict[str, Any]:
|
|
747
|
+
"""Get statistics about tool configurations"""
|
|
748
|
+
return self.config_manager.get_stats()
|
|
749
|
+
|
|
750
|
+
# Convenience methods for common operations
|
|
751
|
+
def flash_esp32(self, port: str, firmware_path: str, **kwargs) -> Dict[str, Any]:
|
|
752
|
+
"""Convenience method for ESP32 flashing"""
|
|
753
|
+
if not self.is_available('esptool'):
|
|
754
|
+
return {"status": "error", "message": "esptool not available"}
|
|
755
|
+
|
|
756
|
+
args = [
|
|
757
|
+
'--chip', kwargs.get('chip', 'esp32s3'),
|
|
758
|
+
'--port', port,
|
|
759
|
+
'--baud', kwargs.get('baud', '460800'),
|
|
760
|
+
'write_flash', kwargs.get('address', '0x10000'), firmware_path
|
|
761
|
+
]
|
|
762
|
+
|
|
763
|
+
result = self.execute('esptool', args, timeout=300)
|
|
764
|
+
return {
|
|
765
|
+
"status": "success" if result.success else "error",
|
|
766
|
+
"message": "Flash completed" if result.success else result.stderr,
|
|
767
|
+
"execution_time": result.execution_time
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
def port_scan(self, target: str, ports: str = "1-1000",
|
|
771
|
+
scan_type: str = "syn") -> Dict[str, Any]:
|
|
772
|
+
"""Convenience method for port scanning"""
|
|
773
|
+
if not self.is_available('nmap'):
|
|
774
|
+
return {"status": "error", "message": "nmap not available"}
|
|
775
|
+
|
|
776
|
+
scan_args = {
|
|
777
|
+
'syn': ['-sS'],
|
|
778
|
+
'tcp': ['-sT'],
|
|
779
|
+
'udp': ['-sU'],
|
|
780
|
+
'ping': ['-sn']
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
args = scan_args.get(scan_type, ['-sS'])
|
|
784
|
+
args.extend(['-p', ports, target])
|
|
785
|
+
|
|
786
|
+
result = self.execute('nmap', args, timeout=300)
|
|
787
|
+
return {
|
|
788
|
+
"status": "success" if result.success else "error",
|
|
789
|
+
"message": "Scan completed" if result.success else result.stderr,
|
|
790
|
+
"output": result.stdout,
|
|
791
|
+
"execution_time": result.execution_time
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
def extract_strings(self, file_path: str, min_length: int = 4) -> Dict[str, Any]:
|
|
795
|
+
"""Convenience method for string extraction"""
|
|
796
|
+
if not self.is_available('strings'):
|
|
797
|
+
return {"status": "error", "message": "strings tool not available"}
|
|
798
|
+
|
|
799
|
+
args = [f'-n{min_length}', file_path]
|
|
800
|
+
result = self.execute('strings', args)
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
"status": "success" if result.success else "error",
|
|
804
|
+
"message": "Strings extracted" if result.success else result.stderr,
|
|
805
|
+
"strings": result.stdout.split('\n') if result.success else [],
|
|
806
|
+
"execution_time": result.execution_time
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
def adb_devices(self) -> Dict[str, Any]:
|
|
810
|
+
"""Convenience method for listing ADB devices"""
|
|
811
|
+
if not self.is_available('adb'):
|
|
812
|
+
return {"status": "error", "message": "adb not available"}
|
|
813
|
+
|
|
814
|
+
result = self.execute('adb', ['devices'])
|
|
815
|
+
|
|
816
|
+
devices = []
|
|
817
|
+
if result.success:
|
|
818
|
+
lines = result.stdout.split('\n')[1:] # Skip header
|
|
819
|
+
for line in lines:
|
|
820
|
+
line = line.strip()
|
|
821
|
+
if line and '\t' in line:
|
|
822
|
+
device_id, status = line.split('\t')
|
|
823
|
+
devices.append({'id': device_id, 'status': status})
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
"status": "success" if result.success else "error",
|
|
827
|
+
"message": "Devices listed" if result.success else result.stderr,
|
|
828
|
+
"devices": devices,
|
|
829
|
+
"execution_time": result.execution_time
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
# ========== Methods from CentralizedToolManager ==========
|
|
833
|
+
|
|
834
|
+
def get_system_status(self) -> SystemStatus:
|
|
835
|
+
"""Get comprehensive system status"""
|
|
836
|
+
validation = self.validate_tools()
|
|
837
|
+
|
|
838
|
+
# Determine health status
|
|
839
|
+
if validation['required_missing']:
|
|
840
|
+
health = SystemHealth.CRITICAL
|
|
841
|
+
elif validation['missing_tools']:
|
|
842
|
+
health = SystemHealth.DEGRADED
|
|
843
|
+
else:
|
|
844
|
+
health = SystemHealth.HEALTHY
|
|
845
|
+
|
|
846
|
+
# Generate recommendations
|
|
847
|
+
recommendations = []
|
|
848
|
+
if validation['required_missing']:
|
|
849
|
+
recommendations.append(f"Install {len(validation['required_missing'])} required tools")
|
|
850
|
+
if len(validation['missing_tools']) > len(validation['required_missing']):
|
|
851
|
+
optional_missing = len(validation['missing_tools']) - len(validation['required_missing'])
|
|
852
|
+
recommendations.append(f"Consider installing {optional_missing} optional tools")
|
|
853
|
+
|
|
854
|
+
return SystemStatus(
|
|
855
|
+
health=health,
|
|
856
|
+
total_tools=validation['total_tools'],
|
|
857
|
+
available_tools=len(validation['available_tools']),
|
|
858
|
+
missing_tools=len(validation['missing_tools']),
|
|
859
|
+
required_missing=len(validation['required_missing']),
|
|
860
|
+
can_operate=validation['can_operate'],
|
|
861
|
+
recommendations=recommendations
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
def get_health_report(self) -> Dict[str, Any]:
|
|
865
|
+
"""Get detailed health report"""
|
|
866
|
+
status = self.get_system_status()
|
|
867
|
+
validation = self.validate_tools()
|
|
868
|
+
|
|
869
|
+
report = {
|
|
870
|
+
'timestamp': self._get_timestamp(),
|
|
871
|
+
'system_health': status.health.value,
|
|
872
|
+
'can_operate': status.can_operate,
|
|
873
|
+
'summary': {
|
|
874
|
+
'total_tools': status.total_tools,
|
|
875
|
+
'available_tools': status.available_tools,
|
|
876
|
+
'missing_tools': status.missing_tools,
|
|
877
|
+
'required_missing': status.required_missing
|
|
878
|
+
},
|
|
879
|
+
'tools': {
|
|
880
|
+
'available': validation['available_tools'],
|
|
881
|
+
'missing': validation['missing_tools'],
|
|
882
|
+
'required_missing': validation['required_missing']
|
|
883
|
+
},
|
|
884
|
+
'recommendations': status.recommendations,
|
|
885
|
+
'backend_status': self.backend_manager.get_status(),
|
|
886
|
+
'queue_status': self.queue_manager.get_stats()
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return report
|
|
890
|
+
|
|
891
|
+
def get_installation_guide(self) -> Dict[str, Any]:
|
|
892
|
+
"""Get installation recommendations with hints"""
|
|
893
|
+
missing_tools = self.get_missing_tools()
|
|
894
|
+
install_hints = self.get_install_hints()
|
|
895
|
+
required_tools = self.get_required_tools()
|
|
896
|
+
optional_tools = self.get_optional_tools()
|
|
897
|
+
|
|
898
|
+
# Separate required and optional missing tools
|
|
899
|
+
required_missing = [t for t in missing_tools if t in required_tools]
|
|
900
|
+
optional_missing = [t for t in missing_tools if t in optional_tools]
|
|
901
|
+
|
|
902
|
+
guide = {
|
|
903
|
+
'required_tools': {},
|
|
904
|
+
'optional_tools': {},
|
|
905
|
+
'priority_order': required_missing + optional_missing[:5] # Top 5 optional
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
for tool in required_missing:
|
|
909
|
+
guide['required_tools'][tool] = {
|
|
910
|
+
'description': self._get_tool_description(tool),
|
|
911
|
+
'install_hint': install_hints.get(tool, "No installation hint available"),
|
|
912
|
+
'priority': 'HIGH'
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
for tool in optional_missing:
|
|
916
|
+
guide['optional_tools'][tool] = {
|
|
917
|
+
'description': self._get_tool_description(tool),
|
|
918
|
+
'install_hint': install_hints.get(tool, "No installation hint available"),
|
|
919
|
+
'priority': 'MEDIUM'
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return guide
|
|
923
|
+
|
|
924
|
+
def _get_tool_description(self, tool_name: str) -> str:
|
|
925
|
+
"""Get tool description from config"""
|
|
926
|
+
tool_config = self.get_tool_config(tool_name)
|
|
927
|
+
return tool_config.description if tool_config else "No description available"
|
|
928
|
+
|
|
929
|
+
def _get_timestamp(self) -> str:
|
|
930
|
+
"""Get current timestamp"""
|
|
931
|
+
from datetime import datetime
|
|
932
|
+
return datetime.now().isoformat()
|
|
933
|
+
|
|
934
|
+
def get_system_health(self, force_refresh: bool = False):
|
|
935
|
+
"""Get system health with category status"""
|
|
936
|
+
status = self.get_system_status()
|
|
937
|
+
validation = self.validate_tools()
|
|
938
|
+
|
|
939
|
+
# Create a health object with category status
|
|
940
|
+
class HealthStatus:
|
|
941
|
+
def __init__(self, status, validation):
|
|
942
|
+
self.status = status.health.value
|
|
943
|
+
self.total_tools = status.total_tools
|
|
944
|
+
self.available_tools = status.available_tools
|
|
945
|
+
self.missing_tools = status.missing_tools
|
|
946
|
+
self.missing_critical_tools = status.required_missing
|
|
947
|
+
self.recommendations = status.recommendations
|
|
948
|
+
self.category_status = {
|
|
949
|
+
"tools": {
|
|
950
|
+
"total_tools": validation['total_tools'],
|
|
951
|
+
"available_tools": validation['available_tools'],
|
|
952
|
+
"missing_tools": validation['missing_tools'],
|
|
953
|
+
"can_operate": validation['can_operate']
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return HealthStatus(status, validation)
|
|
958
|
+
|
|
959
|
+
def get_installation_recommendations(self) -> Dict[str, Dict[str, List[str]]]:
|
|
960
|
+
"""Get installation recommendations by category"""
|
|
961
|
+
missing_tools = self.get_missing_tools()
|
|
962
|
+
required_tools = self.get_required_tools()
|
|
963
|
+
|
|
964
|
+
required_missing = [t for t in missing_tools if t in required_tools]
|
|
965
|
+
optional_missing = [t for t in missing_tools if t not in required_tools]
|
|
966
|
+
|
|
967
|
+
return {
|
|
968
|
+
"tools": {
|
|
969
|
+
"required": required_missing,
|
|
970
|
+
"optional": optional_missing
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
# Queue delegation methods
|
|
975
|
+
def submit_task(self, tool_name: str, args: List[str],
|
|
976
|
+
priority: Union[str, TaskPriority] = "normal", **kwargs) -> str:
|
|
977
|
+
"""Submit a tool execution task to the queue"""
|
|
978
|
+
# Handle both string and enum priority inputs
|
|
979
|
+
if isinstance(priority, TaskPriority):
|
|
980
|
+
task_priority = priority
|
|
981
|
+
else:
|
|
982
|
+
# Convert priority string to enum
|
|
983
|
+
priority_map = {
|
|
984
|
+
'critical': TaskPriority.CRITICAL,
|
|
985
|
+
'high': TaskPriority.HIGH,
|
|
986
|
+
'normal': TaskPriority.NORMAL,
|
|
987
|
+
'low': TaskPriority.LOW,
|
|
988
|
+
'background': TaskPriority.BACKGROUND
|
|
989
|
+
}
|
|
990
|
+
task_priority = priority_map.get(priority.lower(), TaskPriority.NORMAL)
|
|
991
|
+
|
|
992
|
+
# Get tool path
|
|
993
|
+
tool_info = self.registry.get_tool(tool_name)
|
|
994
|
+
if not tool_info:
|
|
995
|
+
raise ValueError(f"Tool '{tool_name}' not found")
|
|
996
|
+
|
|
997
|
+
return self.queue_manager.submit_task(
|
|
998
|
+
tool_name=tool_name,
|
|
999
|
+
tool_path=tool_info.path,
|
|
1000
|
+
args=args,
|
|
1001
|
+
priority=task_priority,
|
|
1002
|
+
**kwargs
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def get_task(self, task_id: str):
|
|
1006
|
+
"""Get task by ID"""
|
|
1007
|
+
return self.queue_manager.get_task(task_id)
|
|
1008
|
+
|
|
1009
|
+
def wait_for_task(self, task_id: str, timeout: Optional[float] = None):
|
|
1010
|
+
"""Wait for task completion"""
|
|
1011
|
+
return self.queue_manager.wait_for_task(task_id, timeout)
|
|
1012
|
+
|
|
1013
|
+
def get_queue_stats(self):
|
|
1014
|
+
"""Get queue statistics"""
|
|
1015
|
+
return self.queue_manager.get_stats()
|
|
1016
|
+
|
|
1017
|
+
# Backend delegation methods
|
|
1018
|
+
def list_execution_backends(self) -> List[str]:
|
|
1019
|
+
"""List available execution backends"""
|
|
1020
|
+
return self.backend_manager.list_available_backends()
|
|
1021
|
+
|
|
1022
|
+
def set_default_backend(self, backend_name: str):
|
|
1023
|
+
"""Set default execution backend"""
|
|
1024
|
+
self.backend_manager.set_default_backend(backend_name)
|
|
1025
|
+
|
|
1026
|
+
# System management
|
|
1027
|
+
def initialize(self) -> bool:
|
|
1028
|
+
"""Initialize the system"""
|
|
1029
|
+
try:
|
|
1030
|
+
logger.info("Initializing tool manager...")
|
|
1031
|
+
|
|
1032
|
+
# Discover tools
|
|
1033
|
+
discovery_results = self.discover_tools()
|
|
1034
|
+
logger.info(f"Discovered {len(discovery_results)} tools")
|
|
1035
|
+
|
|
1036
|
+
# Check system health
|
|
1037
|
+
status = self.get_system_status()
|
|
1038
|
+
logger.info(f"System health: {status.health.value}")
|
|
1039
|
+
|
|
1040
|
+
if status.health == SystemHealth.CRITICAL:
|
|
1041
|
+
logger.warning(f"System is in CRITICAL state - {status.required_missing} required tools missing")
|
|
1042
|
+
|
|
1043
|
+
return True
|
|
1044
|
+
|
|
1045
|
+
except Exception as e:
|
|
1046
|
+
logger.error(f"Failed to initialize system: {e}")
|
|
1047
|
+
return False
|
|
1048
|
+
|
|
1049
|
+
def cleanup(self):
|
|
1050
|
+
"""Cleanup resources"""
|
|
1051
|
+
try:
|
|
1052
|
+
self.queue_manager.shutdown()
|
|
1053
|
+
logger.info("Tool manager cleaned up")
|
|
1054
|
+
except Exception as e:
|
|
1055
|
+
logger.error(f"Error during cleanup: {e}")
|
|
1056
|
+
|
|
1057
|
+
def shutdown(self, wait: bool = True):
|
|
1058
|
+
"""Shutdown the system"""
|
|
1059
|
+
self.cleanup()
|
|
1060
|
+
|
|
1061
|
+
def __enter__(self):
|
|
1062
|
+
"""Context manager entry"""
|
|
1063
|
+
self.initialize()
|
|
1064
|
+
return self
|
|
1065
|
+
|
|
1066
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
1067
|
+
"""Context manager exit"""
|
|
1068
|
+
self.cleanup()
|
|
1069
|
+
|
|
1070
|
+
# Convenience
|
|
1071
|
+
def get_tool_manager() -> ToolManager:
|
|
1072
|
+
"""Get a singleton tool manager instance"""
|
|
1073
|
+
if not hasattr(get_tool_manager, '_instance'):
|
|
1074
|
+
get_tool_manager._instance = ToolManager()
|
|
1075
|
+
return get_tool_manager._instance
|
|
1076
|
+
|
|
1077
|
+
def execute_tool(tool_name: str, args: List[str], **kwargs) -> ExecutionResult:
|
|
1078
|
+
"""Execute a tool using the default tool manager"""
|
|
1079
|
+
return get_tool_manager().execute(tool_name, args, **kwargs)
|
|
1080
|
+
|
|
1081
|
+
def is_tool_available(tool_name: str) -> bool:
|
|
1082
|
+
"""Check if a tool is available"""
|
|
1083
|
+
return get_tool_manager().is_available(tool_name)
|
|
1084
|
+
|
|
1085
|
+
def print_system_report():
|
|
1086
|
+
"""Print a comprehensive system report"""
|
|
1087
|
+
manager = get_tool_manager()
|
|
1088
|
+
|
|
1089
|
+
print("\n" + "="*60)
|
|
1090
|
+
print("š§ IOTSPLOIT TOOL MANAGEMENT SYSTEM REPORT")
|
|
1091
|
+
print("="*60)
|
|
1092
|
+
|
|
1093
|
+
# Get system status
|
|
1094
|
+
status = manager.get_system_status()
|
|
1095
|
+
health_icon = {
|
|
1096
|
+
"healthy": "ā
",
|
|
1097
|
+
"degraded": "ā ļø",
|
|
1098
|
+
"critical": "ā"
|
|
1099
|
+
}.get(status.health.value, "ā")
|
|
1100
|
+
|
|
1101
|
+
print(f"\nš System Health: {health_icon} {status.health.value.upper()}")
|
|
1102
|
+
print(f"š§ Total Tools: {status.total_tools}")
|
|
1103
|
+
print(f"ā
Available: {status.available_tools}")
|
|
1104
|
+
print(f"ā Missing: {status.missing_tools}")
|
|
1105
|
+
print(f"š“ Required Missing: {status.required_missing}")
|
|
1106
|
+
print(f"š Can Operate: {'Yes' if status.can_operate else 'No'}")
|
|
1107
|
+
|
|
1108
|
+
# Show recommendations
|
|
1109
|
+
if status.recommendations:
|
|
1110
|
+
print(f"\nš” Recommendations:")
|
|
1111
|
+
for rec in status.recommendations:
|
|
1112
|
+
print(f" ⢠{rec}")
|
|
1113
|
+
|
|
1114
|
+
# Show queue stats
|
|
1115
|
+
try:
|
|
1116
|
+
queue_stats = manager.get_queue_stats()
|
|
1117
|
+
print(f"\nš Queue Status:")
|
|
1118
|
+
print(f" ⢠Total Tasks: {queue_stats.total_tasks}")
|
|
1119
|
+
print(f" ⢠Running: {queue_stats.running_tasks}")
|
|
1120
|
+
print(f" ⢠Pending: {queue_stats.pending_tasks}")
|
|
1121
|
+
print(f" ⢠Completed: {queue_stats.completed_tasks}")
|
|
1122
|
+
print(f" ⢠Failed: {queue_stats.failed_tasks}")
|
|
1123
|
+
if queue_stats.average_execution_time > 0:
|
|
1124
|
+
print(f" ⢠Avg Execution Time: {queue_stats.average_execution_time:.2f}s")
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
print(f"\nš Queue Status: Error - {e}")
|
|
1127
|
+
|
|
1128
|
+
# Show category info
|
|
1129
|
+
try:
|
|
1130
|
+
category_info = manager.get_category_info()
|
|
1131
|
+
print(f"\nš Tool Category: {category_info.name}")
|
|
1132
|
+
print(f" Description: {category_info.description}")
|
|
1133
|
+
print(f" Tools: {len(category_info.tools)}")
|
|
1134
|
+
|
|
1135
|
+
# Show available vs missing
|
|
1136
|
+
available = manager.get_available_tools()
|
|
1137
|
+
missing = manager.get_missing_tools()
|
|
1138
|
+
required = manager.get_required_tools()
|
|
1139
|
+
|
|
1140
|
+
print(f"\nš§ Tool Breakdown:")
|
|
1141
|
+
print(f" ā
Available: {len(available)}")
|
|
1142
|
+
if available[:5]: # Show first 5
|
|
1143
|
+
for tool in available[:5]:
|
|
1144
|
+
print(f" ⢠{tool}")
|
|
1145
|
+
if len(available) > 5:
|
|
1146
|
+
print(f" ... and {len(available) - 5} more")
|
|
1147
|
+
|
|
1148
|
+
if missing:
|
|
1149
|
+
print(f" ā Missing: {len(missing)}")
|
|
1150
|
+
required_missing = [t for t in missing if t in required]
|
|
1151
|
+
optional_missing = [t for t in missing if t not in required]
|
|
1152
|
+
|
|
1153
|
+
if required_missing:
|
|
1154
|
+
print(f" š“ Required: {', '.join(required_missing[:3])}")
|
|
1155
|
+
if len(required_missing) > 3:
|
|
1156
|
+
print(f" ... and {len(required_missing) - 3} more")
|
|
1157
|
+
|
|
1158
|
+
if optional_missing:
|
|
1159
|
+
print(f" š” Optional: {', '.join(optional_missing[:3])}")
|
|
1160
|
+
if len(optional_missing) > 3:
|
|
1161
|
+
print(f" ... and {len(optional_missing) - 3} more")
|
|
1162
|
+
|
|
1163
|
+
except Exception as e:
|
|
1164
|
+
print(f"\nš Category Status: Error - {e}")
|
|
1165
|
+
|
|
1166
|
+
print("\n" + "="*60)
|