Class: Msf::RPC::JSON::Dispatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/rpc/json/dispatcher.rb

Constant Summary collapse

JSON_RPC_VERSION =
'2.0'
JSON_RPC_REQUIRED_MEMBERS =
%i(jsonrpc method)
JSON_RPC_MEMBER_TYPES =
{
    # A String specifying the version of the JSON-RPC protocol.
    jsonrpc: [String],
    # A String containing the name of the method to be invoked.
    method: [String],
    # If present, parameters for the rpc call MUST be provided as a Structured
    # value. Either by-position through an Array or by-name through an Object.
    # * by-position: params MUST be an Array, containing the values in the
    #   Server expected order.
    # * by-name: params MUST be an Object, with member names that match the
    #   Server expected parameter names. The absence of expected names MAY
    #   result in an error being generated. The names MUST match exactly,
    #   including case, to the method's expected parameters.
    params: [Array, Hash],
    # An identifier established by the Client that MUST contain a String,
    # Number, or NULL value if included. If it is not included it is assumed
    # to be a notification. The value SHOULD normally not be Null [1] and
    # Numbers SHOULD NOT contain fractional parts [2]
    id: [Integer, String, NilClass]
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(framework) ⇒ Dispatcher

Instantiate a Dispatcher.

Parameters:


34
35
36
37
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 34

def initialize(framework)
  @framework = framework
  @command = nil
end

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command


30
31
32
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 30

def command
  @command
end

#frameworkObject (readonly)

Returns the value of attribute framework


29
30
31
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 29

def framework
  @framework
end

Class Method Details

.add_response_id_member(response, request) ⇒ Object

Adds response id based on request id.

Parameters:

  • response (Hash)

    the JSON-RPC response

  • request (Hash)

    the JSON-RPC request


212
213
214
215
216
217
218
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 212

def self.add_response_id_member(response, request)
  if !request.nil? && request.key?(:id)
    response[:id] = request[:id]
  else
    response[:id] = nil
  end
end

.create_error_response(error, request = nil) ⇒ Object

Create a JSON-RPC error response.

Parameters:

  • error (RpcError)

    a RpcError instance

  • request (Hash) (defaults to: nil)

    the JSON-RPC request


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 193

def self.create_error_response(error, request = nil)
  response = {
      # A String specifying the version of the JSON-RPC protocol.
      jsonrpc: JSON_RPC_VERSION,

      # This member is REQUIRED on error.
      # This member MUST NOT exist if there was no error triggered during invocation.
      # The value for this member MUST be an Object as defined in section 5.1.
      error: error.to_h
  }

  self.add_response_id_member(response, request)

  response
end

.create_success_response(result, request = nil) ⇒ Object

Create a JSON-RPC success response.

Parameters:

  • result (Object)

    the RPC method's return value

  • request (Hash) (defaults to: nil)

    the JSON-RPC request


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 173

def self.create_success_response(result, request = nil)
  response = {
      # A String specifying the version of the JSON-RPC protocol.
      jsonrpc: JSON_RPC_VERSION,

      # This member is REQUIRED on success.
      # This member MUST NOT exist if there was an error invoking the method.
      # The value of this member is determined by the method invoked on the Server.
      result: result
  }

  self.add_response_id_member(response, request)

  response
end

.to_json(data) ⇒ String

Serialize data as JSON string.

Parameters:

  • data (Hash)

    data

Returns:

  • (String)

    data serialized JSON string if data not nil; otherwise, nil.


162
163
164
165
166
167
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 162

def self.to_json(data)
  return nil if data.nil?

  json = data.to_json
  return json.to_s
end

Instance Method Details

#parse_json_request(source) ⇒ Hash or Array

Parse the JSON document source into a Hash or Array with symbols for the names (keys). An error occurred on the server while parsing the JSON text.

Parameters:

  • source (String)

    the JSON source

Returns:

  • (Hash or Array)

    Hash or Array representation of source

Raises:

  • (ParseError)

    Invalid JSON was received by the server.


151
152
153
154
155
156
157
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 151

def parse_json_request(source)
  begin
    JSON.parse(source, symbolize_names: true)
  rescue
    raise ParseError.new
  end
end

#process(source) ⇒ String

Process the JSON-RPC request. if successful; otherwise, a JSON-RPC error response.

Parameters:

  • source (String)

    the JSON-RPC request

Returns:

  • (String)

    JSON-RPC response that encapsulates the RPC result


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 49

def process(source)
  begin
    request = parse_json_request(source)
    if request.is_a?(Array)
      # If the batch rpc call itself fails to be recognized as an valid
      # JSON or as an Array with at least one value, the response from
      # the Server MUST be a single Response object.
      raise InvalidRequest.new if request.empty?
      # process batch request
      response = request.map { |r| process_request(r) }
      # A Response object SHOULD exist for each Request object, except that
      # there SHOULD NOT be any Response objects for notifications.
      # Remove nil responses from response array
      response.compact!
    else
      response = process_request(request)
    end
  rescue ParseError, InvalidRequest => e
    # If there was an error in detecting the id in the Request object
    # (e.g. Parse error/Invalid Request), then the id member MUST be
    # Null. Don't pass request obj when building the error response.
    response = self.class.create_error_response(e)
  rescue RpcError => e
    # other JSON-RPC errors should include the id from the Request object
    response = self.class.create_error_response(e, request)
  rescue => e
    response = self.class.create_error_response(ApplicationServerError.new(e), request)
  end

  # When a rpc call is made, the Server MUST reply with a Response, except
  # for in the case of Notifications. The Response is expressed as a single
  # JSON Object.
  self.class.to_json(response)
end

#process_request(request) ⇒ Object

Validate and execute the JSON-RPC request. Msf::RPC::Exception that occurred during execution. if successful; otherwise, a JSON-RPC error response.

Parameters:

  • request (Hash)

    the JSON-RPC request

Raises:


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 92

def process_request(request)
  begin
    if !validate_rpc_request(request)
      response = self.class.create_error_response(InvalidRequest.new)
      return response
    end

    # dispatch method execution to command
    result = @command.execute(request[:method], request[:params])

    # A Notification is a Request object without an "id" member. A Request
    # object that is a Notification signifies the Client's lack of interest
    # in the corresponding Response object, and as such no Response object
    # needs to be returned to the client. The Server MUST NOT reply to a
    # Notification, including those that are within a batch request.
    if request.key?(:id)
      response = self.class.create_success_response(result, request)
    else
      response = nil
    end

    response
  rescue Msf::OptionValidateError => e
    raise InvalidParams.new(data: { options: e.options, message: e.message })
  rescue ::NoMethodError => e
    raise MethodNotFound.new(e.name, data: { method: e.name, message: e.message })
  rescue ArgumentError
    raise InvalidParams.new
  rescue Msf::RPC::Exception => e
    raise ApplicationServerError.new(e.message, data: { code: e.code })
  end
end

#set_command(command) ⇒ Object

Set the command.

Parameters:

  • command (RpcCommand)

    the command used by the Dispatcher.


41
42
43
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 41

def set_command(command)
  @command = command
end

#validate_rpc_request(request) ⇒ Object

Validate the JSON-RPC request.

Parameters:

  • request (Hash)

    the JSON-RPC request


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 128

def validate_rpc_request(request)
  # validate request is an object
  return false unless request.is_a?(Hash)

  # validate request contains required members
  JSON_RPC_REQUIRED_MEMBERS.each { |member| return false unless request.key?(member) }

  return false if request[:jsonrpc] != JSON_RPC_VERSION

  # validate request members are correct types
  request.each do |member, value|
    return false if JSON_RPC_MEMBER_TYPES.key?(member) &&
        !JSON_RPC_MEMBER_TYPES[member].one? { |type| value.is_a?(type) }
  end

  true
end