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.
Files changed (41) hide show
  1. iotsploit_core/__init__.py +7 -0
  2. iotsploit_core/context.py +27 -0
  3. iotsploit_core/core/__init__.py +0 -0
  4. iotsploit_core/core/base_plugin.py +290 -0
  5. iotsploit_core/core/device_config.py +105 -0
  6. iotsploit_core/core/device_manager.py +942 -0
  7. iotsploit_core/core/device_registry.py +118 -0
  8. iotsploit_core/core/device_scanner.py +74 -0
  9. iotsploit_core/core/device_spec.py +105 -0
  10. iotsploit_core/core/device_store.py +86 -0
  11. iotsploit_core/core/execution_backend.py +464 -0
  12. iotsploit_core/core/execution_queue.py +456 -0
  13. iotsploit_core/core/exploit_manager.py +728 -0
  14. iotsploit_core/core/exploit_spec.py +73 -0
  15. iotsploit_core/core/stream_manager.py +157 -0
  16. iotsploit_core/core/tool_config.py +430 -0
  17. iotsploit_core/core/tool_manager.py +1166 -0
  18. iotsploit_core/core/tool_service.py +936 -0
  19. iotsploit_core/domain/__init__.py +3 -0
  20. iotsploit_core/domain/device.py +70 -0
  21. iotsploit_core/domain/execution_plan.py +29 -0
  22. iotsploit_core/domain/plugin.py +17 -0
  23. iotsploit_core/domain/stream.py +63 -0
  24. iotsploit_core/domain/target.py +243 -0
  25. iotsploit_core/platforms/__init__.py +19 -0
  26. iotsploit_core/platforms/consts.py +25 -0
  27. iotsploit_core/ports/__init__.py +6 -0
  28. iotsploit_core/ports/driver_state_repo.py +15 -0
  29. iotsploit_core/ports/plugin_repo.py +26 -0
  30. iotsploit_core/ports/stream_backend.py +28 -0
  31. iotsploit_core/ports/task_runner.py +22 -0
  32. iotsploit_core/ports/wifi_backend.py +148 -0
  33. iotsploit_core/py.typed +2 -0
  34. iotsploit_core/utils/__init__.py +53 -0
  35. iotsploit_core/utils/exceptions.py +152 -0
  36. iotsploit_core/utils/helpers.py +65 -0
  37. iotsploit_core/utils/iots_logger.py +164 -0
  38. iotsploit_core/utils/result.py +41 -0
  39. iotsploit_core-0.0.6.dist-info/METADATA +117 -0
  40. iotsploit_core-0.0.6.dist-info/RECORD +41 -0
  41. iotsploit_core-0.0.6.dist-info/WHEEL +4 -0
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Execution Backend Abstraction for IoTSploit Tool Manager
4
+ ========================================================
5
+
6
+ Provides multiple execution backends for tool execution:
7
+ - Subprocess Backend (default)
8
+ - Invoke Backend (task automation)
9
+ - sh Backend (pythonic shell)
10
+ - Sarge Backend (subprocess wrapper)
11
+
12
+ This allows plugins to choose the best execution method for their needs.
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ import time
18
+ import logging
19
+ import subprocess
20
+ from abc import ABC, abstractmethod
21
+ from typing import Dict, List, Optional, Any, Union
22
+ from dataclasses import dataclass
23
+ from enum import Enum
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ @dataclass
28
+ class ExecutionResult:
29
+ """Standardized execution result across all backends"""
30
+ success: bool
31
+ return_code: int
32
+ stdout: str
33
+ stderr: str
34
+ execution_time: float
35
+ command: str
36
+ tool_path: str
37
+ backend: str
38
+ metadata: Dict[str, Any] = None
39
+
40
+ class ExecutionBackend(ABC):
41
+ """Abstract base class for execution backends"""
42
+
43
+ def __init__(self, name: str):
44
+ self.name = name
45
+ self.logger = logging.getLogger(f"execution.{name}")
46
+
47
+ @abstractmethod
48
+ def execute(self, tool_path: str, args: List[str],
49
+ working_dir: Optional[str] = None,
50
+ env: Optional[Dict[str, str]] = None,
51
+ timeout: Optional[int] = None) -> ExecutionResult:
52
+ """Execute a tool with given arguments"""
53
+ pass
54
+
55
+ @abstractmethod
56
+ def is_available(self) -> bool:
57
+ """Check if this backend is available"""
58
+ pass
59
+
60
+ def _prepare_environment(self, env: Optional[Dict[str, str]] = None) -> Dict[str, str]:
61
+ """Prepare execution environment"""
62
+ exec_env = os.environ.copy()
63
+ if env:
64
+ exec_env.update(env)
65
+ return exec_env
66
+
67
+ class SubprocessBackend(ExecutionBackend):
68
+ """Default subprocess-based execution backend"""
69
+
70
+ def __init__(self):
71
+ super().__init__("subprocess")
72
+
73
+ def execute(self, tool_path: str, args: List[str],
74
+ working_dir: Optional[str] = None,
75
+ env: Optional[Dict[str, str]] = None,
76
+ timeout: Optional[int] = None) -> ExecutionResult:
77
+ """Execute using subprocess module"""
78
+
79
+ command = [tool_path] + args
80
+ command_str = ' '.join(command)
81
+ exec_env = self._prepare_environment(env)
82
+
83
+ self.logger.debug(f"Executing: {command_str}")
84
+ start_time = time.time()
85
+
86
+ try:
87
+ result = subprocess.run(
88
+ command,
89
+ cwd=working_dir,
90
+ env=exec_env,
91
+ capture_output=True,
92
+ text=True,
93
+ timeout=timeout
94
+ )
95
+
96
+ execution_time = time.time() - start_time
97
+
98
+ return ExecutionResult(
99
+ success=result.returncode == 0,
100
+ return_code=result.returncode,
101
+ stdout=result.stdout,
102
+ stderr=result.stderr,
103
+ execution_time=execution_time,
104
+ command=command_str,
105
+ tool_path=tool_path,
106
+ backend=self.name
107
+ )
108
+
109
+ except subprocess.TimeoutExpired as e:
110
+ execution_time = time.time() - start_time
111
+ return ExecutionResult(
112
+ success=False,
113
+ return_code=-1,
114
+ stdout=e.stdout.decode() if e.stdout else "",
115
+ stderr=f"Command timed out after {timeout} seconds",
116
+ execution_time=execution_time,
117
+ command=command_str,
118
+ tool_path=tool_path,
119
+ backend=self.name
120
+ )
121
+ except Exception as e:
122
+ execution_time = time.time() - start_time
123
+ return ExecutionResult(
124
+ success=False,
125
+ return_code=-1,
126
+ stdout="",
127
+ stderr=str(e),
128
+ execution_time=execution_time,
129
+ command=command_str,
130
+ tool_path=tool_path,
131
+ backend=self.name
132
+ )
133
+
134
+ def is_available(self) -> bool:
135
+ """Subprocess is always available"""
136
+ return True
137
+
138
+ class InvokeBackend(ExecutionBackend):
139
+ """Invoke-based execution backend for task automation"""
140
+
141
+ def __init__(self):
142
+ super().__init__("invoke")
143
+ self._invoke_available = self._check_invoke()
144
+
145
+ def _check_invoke(self) -> bool:
146
+ """Check if invoke is available"""
147
+ try:
148
+ import invoke
149
+ return True
150
+ except ImportError:
151
+ return False
152
+
153
+ def execute(self, tool_path: str, args: List[str],
154
+ working_dir: Optional[str] = None,
155
+ env: Optional[Dict[str, str]] = None,
156
+ timeout: Optional[int] = None) -> ExecutionResult:
157
+ """Execute using invoke"""
158
+
159
+ if not self._invoke_available:
160
+ raise RuntimeError("Invoke backend not available")
161
+
162
+ import invoke
163
+ from invoke.exceptions import CommandTimedOut, UnexpectedExit
164
+
165
+ command_str = f"{tool_path} {' '.join(args)}"
166
+ exec_env = self._prepare_environment(env)
167
+
168
+ self.logger.debug(f"Executing with invoke: {command_str}")
169
+ start_time = time.time()
170
+
171
+ try:
172
+ # Create context with environment
173
+ ctx = invoke.Context()
174
+ if working_dir:
175
+ ctx.cd(working_dir)
176
+
177
+ # Execute command
178
+ result = ctx.run(
179
+ command_str,
180
+ env=exec_env,
181
+ hide=True,
182
+ warn=True,
183
+ timeout=timeout
184
+ )
185
+
186
+ execution_time = time.time() - start_time
187
+
188
+ return ExecutionResult(
189
+ success=result.ok,
190
+ return_code=result.return_code,
191
+ stdout=result.stdout,
192
+ stderr=result.stderr,
193
+ execution_time=execution_time,
194
+ command=command_str,
195
+ tool_path=tool_path,
196
+ backend=self.name,
197
+ metadata={'invoke_result': result}
198
+ )
199
+
200
+ except CommandTimedOut as e:
201
+ execution_time = time.time() - start_time
202
+ return ExecutionResult(
203
+ success=False,
204
+ return_code=-1,
205
+ stdout=e.result.stdout if e.result else "",
206
+ stderr=f"Command timed out after {timeout} seconds",
207
+ execution_time=execution_time,
208
+ command=command_str,
209
+ tool_path=tool_path,
210
+ backend=self.name
211
+ )
212
+ except Exception as e:
213
+ execution_time = time.time() - start_time
214
+ return ExecutionResult(
215
+ success=False,
216
+ return_code=-1,
217
+ stdout="",
218
+ stderr=str(e),
219
+ execution_time=execution_time,
220
+ command=command_str,
221
+ tool_path=tool_path,
222
+ backend=self.name
223
+ )
224
+
225
+ def is_available(self) -> bool:
226
+ """Check if invoke is available"""
227
+ return self._invoke_available
228
+
229
+ class ShBackend(ExecutionBackend):
230
+ """sh-based execution backend for pythonic shell operations"""
231
+
232
+ def __init__(self):
233
+ super().__init__("sh")
234
+ self._sh_available = self._check_sh()
235
+
236
+ def _check_sh(self) -> bool:
237
+ """Check if sh is available"""
238
+ try:
239
+ import sh
240
+ return True
241
+ except ImportError:
242
+ return False
243
+
244
+ def execute(self, tool_path: str, args: List[str],
245
+ working_dir: Optional[str] = None,
246
+ env: Optional[Dict[str, str]] = None,
247
+ timeout: Optional[int] = None) -> ExecutionResult:
248
+ """Execute using sh"""
249
+
250
+ if not self._sh_available:
251
+ raise RuntimeError("sh backend not available")
252
+
253
+ import sh
254
+ from sh import CommandNotFound, TimeoutException
255
+
256
+ command_str = f"{tool_path} {' '.join(args)}"
257
+ exec_env = self._prepare_environment(env)
258
+
259
+ self.logger.debug(f"Executing with sh: {command_str}")
260
+ start_time = time.time()
261
+
262
+ try:
263
+ # Get the command
264
+ tool_name = os.path.basename(tool_path)
265
+ if hasattr(sh, tool_name):
266
+ cmd = getattr(sh, tool_name)
267
+ else:
268
+ cmd = sh.Command(tool_path)
269
+
270
+ # Execute with sh
271
+ result = cmd(
272
+ *args,
273
+ _cwd=working_dir,
274
+ _env=exec_env,
275
+ _timeout=timeout,
276
+ _return_cmd=True
277
+ )
278
+
279
+ execution_time = time.time() - start_time
280
+
281
+ return ExecutionResult(
282
+ success=result.exit_code == 0,
283
+ return_code=result.exit_code,
284
+ stdout=str(result.stdout),
285
+ stderr=str(result.stderr),
286
+ execution_time=execution_time,
287
+ command=command_str,
288
+ tool_path=tool_path,
289
+ backend=self.name,
290
+ metadata={'sh_result': result}
291
+ )
292
+
293
+ except TimeoutException as e:
294
+ execution_time = time.time() - start_time
295
+ return ExecutionResult(
296
+ success=False,
297
+ return_code=-1,
298
+ stdout="",
299
+ stderr=f"Command timed out after {timeout} seconds",
300
+ execution_time=execution_time,
301
+ command=command_str,
302
+ tool_path=tool_path,
303
+ backend=self.name
304
+ )
305
+ except Exception as e:
306
+ execution_time = time.time() - start_time
307
+ return ExecutionResult(
308
+ success=False,
309
+ return_code=-1,
310
+ stdout="",
311
+ stderr=str(e),
312
+ execution_time=execution_time,
313
+ command=command_str,
314
+ tool_path=tool_path,
315
+ backend=self.name
316
+ )
317
+
318
+ def is_available(self) -> bool:
319
+ """Check if sh is available"""
320
+ return self._sh_available
321
+
322
+ class SargeBackend(ExecutionBackend):
323
+ """Sarge-based execution backend for advanced subprocess operations"""
324
+
325
+ def __init__(self):
326
+ super().__init__("sarge")
327
+ self._sarge_available = self._check_sarge()
328
+
329
+ def _check_sarge(self) -> bool:
330
+ """Check if sarge is available"""
331
+ try:
332
+ import sarge
333
+ return True
334
+ except ImportError:
335
+ return False
336
+
337
+ def execute(self, tool_path: str, args: List[str],
338
+ working_dir: Optional[str] = None,
339
+ env: Optional[Dict[str, str]] = None,
340
+ timeout: Optional[int] = None) -> ExecutionResult:
341
+ """Execute using sarge"""
342
+
343
+ if not self._sarge_available:
344
+ raise RuntimeError("Sarge backend not available")
345
+
346
+ import sarge
347
+
348
+ command_str = f"{tool_path} {' '.join(args)}"
349
+ exec_env = self._prepare_environment(env)
350
+
351
+ self.logger.debug(f"Executing with sarge: {command_str}")
352
+ start_time = time.time()
353
+
354
+ try:
355
+ # Execute with sarge
356
+ process = sarge.run(
357
+ command_str,
358
+ cwd=working_dir,
359
+ env=exec_env,
360
+ stdout=sarge.Capture(),
361
+ stderr=sarge.Capture(),
362
+ async_=False
363
+ )
364
+
365
+ # Wait for completion with timeout
366
+ if timeout:
367
+ process.wait(timeout=timeout)
368
+ else:
369
+ process.wait()
370
+
371
+ execution_time = time.time() - start_time
372
+
373
+ return ExecutionResult(
374
+ success=process.returncode == 0,
375
+ return_code=process.returncode,
376
+ stdout=process.stdout.text if process.stdout else "",
377
+ stderr=process.stderr.text if process.stderr else "",
378
+ execution_time=execution_time,
379
+ command=command_str,
380
+ tool_path=tool_path,
381
+ backend=self.name,
382
+ metadata={'sarge_process': process}
383
+ )
384
+
385
+ except Exception as e:
386
+ execution_time = time.time() - start_time
387
+ return ExecutionResult(
388
+ success=False,
389
+ return_code=-1,
390
+ stdout="",
391
+ stderr=str(e),
392
+ execution_time=execution_time,
393
+ command=command_str,
394
+ tool_path=tool_path,
395
+ backend=self.name
396
+ )
397
+
398
+ def is_available(self) -> bool:
399
+ """Check if sarge is available"""
400
+ return self._sarge_available
401
+
402
+ class ExecutionBackendManager:
403
+ """Manages multiple execution backends"""
404
+
405
+ def __init__(self):
406
+ self.backends: Dict[str, ExecutionBackend] = {}
407
+ self.default_backend = "subprocess"
408
+ self._initialize_backends()
409
+
410
+ def _initialize_backends(self):
411
+ """Initialize all available backends"""
412
+ backends = [
413
+ SubprocessBackend(),
414
+ InvokeBackend(),
415
+ ShBackend(),
416
+ SargeBackend()
417
+ ]
418
+
419
+ for backend in backends:
420
+ if backend.is_available():
421
+ self.backends[backend.name] = backend
422
+ logger.debug(f"Registered execution backend: {backend.name}")
423
+ else:
424
+ logger.debug(f"Backend not available: {backend.name}")
425
+
426
+ def get_backend(self, name: Optional[str] = None) -> ExecutionBackend:
427
+ """Get execution backend by name"""
428
+ backend_name = name or self.default_backend
429
+
430
+ if backend_name not in self.backends:
431
+ if backend_name == self.default_backend:
432
+ raise RuntimeError(f"Default backend '{backend_name}' not available")
433
+ else:
434
+ logger.warning(f"Backend '{backend_name}' not available, falling back to default")
435
+ backend_name = self.default_backend
436
+
437
+ return self.backends[backend_name]
438
+
439
+ def list_available_backends(self) -> List[str]:
440
+ """List all available backends"""
441
+ return list(self.backends.keys())
442
+
443
+ def set_default_backend(self, name: str):
444
+ """Set default execution backend"""
445
+ if name not in self.backends:
446
+ raise ValueError(f"Backend '{name}' not available")
447
+ self.default_backend = name
448
+ logger.info(f"Default execution backend set to: {name}")
449
+
450
+ def execute(self, tool_path: str, args: List[str],
451
+ backend: Optional[str] = None, **kwargs) -> ExecutionResult:
452
+ """Execute using specified or default backend"""
453
+ execution_backend = self.get_backend(backend)
454
+ return execution_backend.execute(tool_path, args, **kwargs)
455
+
456
+ # Singleton instance
457
+ _backend_manager = None
458
+
459
+ def get_execution_backend_manager() -> ExecutionBackendManager:
460
+ """Get singleton execution backend manager"""
461
+ global _backend_manager
462
+ if _backend_manager is None:
463
+ _backend_manager = ExecutionBackendManager()
464
+ return _backend_manager