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:



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

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

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command.



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

def command
  @command
end

#frameworkObject (readonly)

Returns the value of attribute framework.



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

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



208
209
210
211
212
213
214
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 208

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) ⇒ Hash

Create a JSON-RPC error response.

Parameters:

  • error (RpcError)

    a RpcError instance

  • request (Hash) (defaults to: nil)

    the JSON-RPC request

Returns:

  • (Hash)

    JSON-RPC error response.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 189

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) ⇒ Hash

Create a JSON-RPC success response.

Parameters:

  • result (Object)

    the RPC method’s return value

  • request (Hash) (defaults to: nil)

    the JSON-RPC request

Returns:

  • (Hash)

    JSON-RPC success response.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 169

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.



158
159
160
161
162
163
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 158

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.



147
148
149
150
151
152
153
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 147

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



48
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
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 48

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) ⇒ Hash?

Validate and execute the JSON-RPC request.

Parameters:

  • request (Hash)

    the JSON-RPC request

Returns:

  • (Hash, nil)

    JSON-RPC response that encapsulates the RPC result, or Nil if a Notification request was sent.

Raises:

  • (InvalidParams)

    ArgumentError occurred during execution.

  • (ApplicationServerError)

    General server-error wrapper around an Msf::RPC::Exception that occurred during execution.



88
89
90
91
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
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 88

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.



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

def set_command(command)
  @command = command
end

#validate_rpc_request(request) ⇒ Boolean

Validate the JSON-RPC request.

Parameters:

  • request (Hash)

    the JSON-RPC request

Returns:

  • (Boolean)

    true if the JSON-RPC request is valid; otherwise, false.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/msf/core/rpc/json/dispatcher.rb', line 124

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