11import logging
2+ import shutil
3+ import subprocess
24import time
3- from typing import Any , Dict
5+ from typing import Any , Dict , Optional
46
57logger = logging .getLogger ("pentest-agent.msf" )
68
7- # Mapping service → recommended auxiliary/exploit modules
89SERVICE_MODULES = {
910 "smb" : [
1011 "auxiliary/scanner/smb/smb_ms17_010" ,
3031}
3132
3233
34+ def _try_connect (password : str , port : int ):
35+ """Attempt a single connection to MSF RPC. Returns client or None."""
36+ try :
37+ from pymetasploit3 .msfrpc import MsfRpcClient
38+ return MsfRpcClient (password = password , port = port , ssl = False )
39+ except Exception :
40+ return None
41+
42+
43+ def start_msfrpcd (password : str = "msfrpc" , port : int = 55553 ):
44+ """
45+ Try to connect to MSF RPC. If unavailable, start msfrpcd automatically
46+ and wait up to 30 seconds for it to become ready.
47+ Returns a connected client or None.
48+ """
49+ # Already running?
50+ client = _try_connect (password , port )
51+ if client :
52+ logger .info (f"Metasploit RPC already running on port { port } " )
53+ return client
54+
55+ if not shutil .which ("msfrpcd" ):
56+ logger .warning ("msfrpcd not found in PATH — skipping MSF modules" )
57+ return None
58+
59+ logger .info ("Starting Metasploit RPC daemon..." )
60+ subprocess .Popen (
61+ ["msfrpcd" , "-P" , password , "-p" , str (port ), "-S" , "-f" ],
62+ stdout = subprocess .DEVNULL ,
63+ stderr = subprocess .DEVNULL ,
64+ )
65+
66+ for i in range (30 ):
67+ time .sleep (1 )
68+ client = _try_connect (password , port )
69+ if client :
70+ logger .info (f"Metasploit RPC daemon started successfully (took { i + 1 } s)" )
71+ return client
72+ logger .debug (f"Waiting for MSF RPC... ({ i + 1 } /30)" )
73+
74+ logger .warning ("Failed to start Metasploit RPC after 30s — skipping MSF modules" )
75+ return None
76+
77+
3378class MetasploitEngine :
3479 def __init__ (self , password : str = "msfrpc" , port : int = 55553 , timeout : int = 120 ):
3580 self .password = password
@@ -38,29 +83,23 @@ def __init__(self, password: str = "msfrpc", port: int = 55553, timeout: int = 1
3883 self ._client = None
3984
4085 def _connect (self ) -> bool :
41- try :
42- from pymetasploit3 .msfrpc import MsfRpcClient
43- self ._client = MsfRpcClient (
44- password = self .password ,
45- port = self .port ,
46- ssl = False ,
47- )
86+ self ._client = start_msfrpcd (self .password , self .port )
87+ if self ._client :
4888 logger .info (f"Connected to Metasploit RPC on port { self .port } " )
4989 return True
50- except Exception as exc :
51- logger .warning (f"Metasploit RPC unavailable: { exc } " )
52- return False
90+ logger .warning ("Metasploit RPC unavailable — skipping MSF modules" )
91+ return False
5392
5493 def run_module (self , target : str , port : str , module_path : str ) -> Dict [str , Any ]:
5594 if not self ._connect ():
5695 return {
5796 "status" : "skipped" ,
58- "output" : "Metasploit RPC not available — start with: msfconsole -x 'load msgrpc Pass=msfrpc' " ,
97+ "output" : "Metasploit RPC unavailable - skipping MSF modules " ,
5998 }
6099
61100 try :
62101 client = self ._client
63- module_type = module_path .split ("/" )[0 ] # auxiliary or exploit
102+ module_type = module_path .split ("/" )[0 ]
64103
65104 if module_type == "auxiliary" :
66105 mod = client .modules .use ("auxiliary" , "/" .join (module_path .split ("/" )[1 :]))
@@ -83,11 +122,9 @@ def run_module(self, target: str, port: str, module_path: str) -> Dict[str, Any]
83122
84123 logger .info (f"MSF module launched: { module_path } (job { job_id } )" )
85124
86- # Wait for completion
87125 elapsed = 0
88126 while elapsed < self .timeout :
89- jobs = client .jobs .list
90- if str (job_id ) not in jobs :
127+ if str (job_id ) not in client .jobs .list :
91128 break
92129 time .sleep (2 )
93130 elapsed += 2
0 commit comments