Async context manager for creating and reliably destroying an MSF console.
()
| 163 | |
| 164 | @contextlib.asynccontextmanager |
| 165 | async def get_msf_console() -> MsfConsole: |
| 166 | """ |
| 167 | Async context manager for creating and reliably destroying an MSF console. |
| 168 | """ |
| 169 | client = get_msf_client() # Raises ConnectionError if not initialized |
| 170 | console_object: Optional[MsfConsole] = None |
| 171 | console_id_str: Optional[str] = None |
| 172 | try: |
| 173 | logger.debug("Creating temporary MSF console...") |
| 174 | # Create console object directly |
| 175 | console_object = await asyncio.to_thread(lambda: client.consoles.console()) |
| 176 | |
| 177 | # Get ID using .cid attribute |
| 178 | if isinstance(console_object, MsfConsole) and hasattr(console_object, 'cid'): |
| 179 | console_id_val = getattr(console_object, 'cid') |
| 180 | console_id_str = str(console_id_val) if console_id_val is not None else None |
| 181 | if not console_id_str: |
| 182 | raise ValueError("Console object created, but .cid attribute is empty or None.") |
| 183 | logger.info(f"MSF console created (ID: {console_id_str})") |
| 184 | |
| 185 | # Read initial prompt/banner to clear buffer and ensure readiness |
| 186 | await asyncio.sleep(0.2) # Short delay for prompt to appear |
| 187 | initial_read = await asyncio.to_thread(lambda: console_object.read()) |
| 188 | logger.debug(f"Initial console read (clearing buffer): {initial_read}") |
| 189 | yield console_object # Yield the ready console object |
| 190 | else: |
| 191 | # This case should ideally not happen if .console() works as expected |
| 192 | logger.error(f"client.consoles.console() did not return expected MsfConsole object with .cid. Got type: {type(console_object)}") |
| 193 | raise MsfRpcError(f"Unexpected result from console creation: {console_object}") |
| 194 | |
| 195 | except MsfRpcError as e: |
| 196 | logger.error(f"MsfRpcError during console operation: {e}") |
| 197 | raise MsfRpcError(f"Error creating/accessing MSF console: {e}") from e |
| 198 | except Exception as e: |
| 199 | logger.exception("Unexpected error during console creation/setup") |
| 200 | raise RuntimeError(f"Unexpected error during console operation: {e}") from e |
| 201 | finally: |
| 202 | # Destruction Logic |
| 203 | if console_id_str and _msf_client_instance: # Check client still exists |
| 204 | try: |
| 205 | logger.info(f"Attempting to destroy Metasploit console (ID: {console_id_str})...") |
| 206 | # Use lambda to avoid potential issues with capture |
| 207 | destroy_result = await asyncio.to_thread( |
| 208 | lambda cid=console_id_str: _msf_client_instance.consoles.destroy(cid) |
| 209 | ) |
| 210 | logger.debug(f"Console destroy result: {destroy_result}") |
| 211 | except Exception as e: |
| 212 | # Log error but don't raise exception during cleanup |
| 213 | logger.error(f"Error destroying MSF console {console_id_str}: {e}") |
| 214 | elif console_object and not console_id_str: |
| 215 | logger.warning("Console object created but no valid ID obtained, cannot explicitly destroy.") |
| 216 | # else: logger.debug("No console ID obtained, skipping destruction.") |
| 217 | |
| 218 | async def run_command_safely(console: MsfConsole, cmd: str, execution_timeout: Optional[int] = None) -> str: |
| 219 | """ |