Class: Msf::MCP::RpcManager

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/mcp/rpc_manager.rb

Overview

Manages the lifecycle of a Metasploit RPC server process.

Probes the configured RPC port, auto-starts the server via Process.spawn of msfrpcd, and cleans up the child process on shutdown.

Constant Summary collapse

LOCALHOST_HOSTS =
%w[localhost 127.0.0.1 ::1].freeze
DEFAULT_WAIT_TIMEOUT =
30
DEFAULT_WAIT_INTERVAL =
1
STOP_GRACE_PERIOD =
5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config:, output:) ⇒ RpcManager

Returns a new instance of RpcManager.

Parameters:

  • config (Hash)

    Application configuration hash

  • output (IO)

    Output stream for status messages



21
22
23
24
25
26
# File 'lib/msf/core/mcp/rpc_manager.rb', line 21

def initialize(config:, output:)
  @config = config
  @output = output
  @rpc_pid = nil
  @rpc_managed = false
end

Instance Attribute Details

#rpc_pidObject (readonly)

Returns the value of attribute rpc_pid.



17
18
19
# File 'lib/msf/core/mcp/rpc_manager.rb', line 17

def rpc_pid
  @rpc_pid
end

Instance Method Details

#auto_start_enabled?Boolean

Whether auto-start is enabled based on config, API type, and host.

Auto-start is only supported for:

  • MessagePack API type (not JSON-RPC)

  • Localhost connections (cannot start a remote RPC server)

  • When auto_start_rpc config is not explicitly false

Returns:

  • (Boolean)


62
63
64
65
66
67
68
# File 'lib/msf/core/mcp/rpc_manager.rb', line 62

def auto_start_enabled?
  return false if @config[:msf_api][:type] != 'messagepack'
  return false unless localhost?
  return false if @config[:msf_api][:auto_start_rpc] == false

  true
end

#ensure_rpc_availablevoid

This method returns an undefined value.

Ensure an RPC server is available, auto-starting if needed.

When the RPC server is already listening, verifies that credentials (or a token for JSON-RPC) are available for the caller to authenticate.

When the server is not available, auto-start is attempted only for MessagePack on localhost with auto_start_rpc enabled. Random credentials are generated when none are provided.

Raises:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/msf/core/mcp/rpc_manager.rb', line 169

def ensure_rpc_available
  if rpc_available?
    @output.puts 'Metasploit RPC server is already running'
    validate_credentials_for_existing_server!
    return
  end

  if @config[:msf_api][:type] == 'json-rpc'
    raise Msf::MCP::Metasploit::RpcStartupError,
          'RPC server is not running and auto-start is not supported for JSON-RPC API type.'
  end

  unless localhost?
    message = "RPC server is not available at #{@config[:msf_api][:host]}:#{@config[:msf_api][:port]}."
    message << ' Cannot auto-start RPC on remote hosts. Please start the RPC server manually.' if auto_start_enabled?
    raise Msf::MCP::Metasploit::RpcStartupError, message
  end

  unless auto_start_enabled?
    raise Msf::MCP::Metasploit::RpcStartupError,
          "RPC server is not running on #{@config[:msf_api][:host]}:#{@config[:msf_api][:port]} " \
          'and auto-start is disabled.'
  end

  generate_random_credentials unless credentials_provided?
  start_rpc_server
  wait_for_rpc
end

#rpc_available?Boolean

Probe the configured RPC port to check if a server is listening.

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/msf/core/mcp/rpc_manager.rb', line 38

def rpc_available?
  host = @config[:msf_api][:host]
  port = @config[:msf_api][:port]

  socket = Rex::Socket::Tcp.create(
    'PeerHost' => host,
    'PeerPort' => port
  )
  socket.close
  dlog({ message: "RPC server is available at #{Rex::Socket.to_authority(host, port)}" },
       LOG_SOURCE, LOG_DEBUG)
  true
rescue Rex::ConnectionError
  false
end

#rpc_managed?Boolean

Whether this manager started and is managing an RPC server process.

Returns:

  • (Boolean)


31
32
33
# File 'lib/msf/core/mcp/rpc_manager.rb', line 31

def rpc_managed?
  @rpc_managed
end

#start_rpc_servervoid

This method returns an undefined value.

Start the Metasploit RPC server by spawning msfrpcd.

Credentials are passed via environment variables to avoid exposing them on the command line.

Raises:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/msf/core/mcp/rpc_manager.rb', line 77

def start_rpc_server
  if @rpc_managed
    @output.puts 'RPC server is already managed by this process'
    return
  end

  @output.puts 'Starting Metasploit RPC server...'
  ilog({ message: 'Starting Metasploit RPC server' },
       LOG_SOURCE, LOG_INFO)

  unless File.executable?(MSFRPCD_PATH)
    raise Msf::MCP::Metasploit::RpcStartupError,
          'msfrpcd executable not found. Cannot auto-start RPC server.'
  end

  args = build_msfrpcd_args
  env = {
    'MSF_RPC_USER' => @config[:msf_api][:user].to_s,
    'MSF_RPC_PASS' => @config[:msf_api][:password].to_s
  }

  pid = Process.spawn(env, MSFRPCD_PATH, *args, %i[out err] => File::NULL)

  @rpc_pid = pid
  @rpc_managed = true
  @output.puts "RPC server started via msfrpcd (PID: #{pid})"
end

#stop_rpc_servervoid

This method returns an undefined value.

Stop the managed RPC server process.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/msf/core/mcp/rpc_manager.rb', line 136

def stop_rpc_server
  return unless @rpc_managed

  @output.puts 'Stopping managed RPC server...'
  ilog({ message: "Stopping managed RPC server (PID: #{@rpc_pid})" },
       LOG_SOURCE, LOG_INFO)

  begin
    Process.kill('TERM', @rpc_pid)
    graceful_wait
  rescue Errno::ESRCH
    # Process already dead — that's fine
  rescue Errno::EPERM
    @output.puts "Warning: no permission to stop RPC process #{@rpc_pid}"
  end

  @rpc_pid = nil
  @rpc_managed = false
end

#wait_for_rpc(timeout: DEFAULT_WAIT_TIMEOUT, interval: DEFAULT_WAIT_INTERVAL) ⇒ true

Wait for the RPC server to become available.

Parameters:

  • timeout (Integer) (defaults to: DEFAULT_WAIT_TIMEOUT)

    Maximum seconds to wait (default: 30)

  • interval (Integer) (defaults to: DEFAULT_WAIT_INTERVAL)

    Seconds between probes (default: 1)

Returns:

  • (true)

    When the server becomes available

Raises:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/msf/core/mcp/rpc_manager.rb', line 112

def wait_for_rpc(timeout: DEFAULT_WAIT_TIMEOUT, interval: DEFAULT_WAIT_INTERVAL)
  deadline = Time.now + timeout

  loop do
    if rpc_available?
      @output.puts 'RPC server is ready'
      return true
    end

    check_managed_process_alive! if @rpc_managed

    if Time.now >= deadline
      raise Msf::MCP::Metasploit::ConnectionError,
            "Timed out waiting for RPC server after #{timeout} seconds"
    end

    @output.puts 'Waiting for RPC server to become available...'
    sleep(interval)
  end
end