Module: RubySMB::Client::Negotiation

Included in:
RubySMB::Client
Defined in:
lib/ruby_smb/client/negotiation.rb

Overview

This module holds all of the methods backing the #negotiate method

Instance Method Summary collapse

Instance Method Details

#negotiatevoid

This method returns an undefined value.

Handles the entire SMB Multi-Protocol Negotiation from the Client to the Server. It sets state on the client appropriate to the protocol and capabilites negotiated during the exchange. It also keeps track of the negotiated dialect.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/ruby_smb/client/negotiation.rb', line 12

def negotiate
  request_packet  = negotiate_request
  raw_response    = send_recv(request_packet)
  response_packet = negotiate_response(raw_response)
  # The list of dialect identifiers sent to the server is stored
  # internally to be able to retrieve the negotiated dialect later on.
  # This is only valid for SMB1.
  response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
  parse_negotiate_response(response_packet)
rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET
  error = 'Unable to Negotiate with remote host'
  error << ', SMB1 may be disabled' if smb1 && !smb2
  raise RubySMB::Error::NegotiationFailure, error
end

#negotiate_requestRubySMB::SMB1::Packet::NegotiateRequest

Creates the first Negotiate Request Packet according to the SMB version used.

Returns:



32
33
34
35
36
37
38
# File 'lib/ruby_smb/client/negotiation.rb', line 32

def negotiate_request
  if smb1
    smb1_negotiate_request
  elsif smb2
    smb2_negotiate_request
  end
end

#negotiate_response(raw_data) ⇒ RubySMB::SMB1::Packet::NegotiateResponseExtended, RubySMB::SMB2::Packet::NegotiateResponse

Takes the raw response data from the server and tries parse it into a valid Response packet object. This method currently assumes that all SMB1 will use Extended Security.

Parameters:

  • raw_data (String)

    the raw binary response from the server

Returns:



47
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
82
83
84
# File 'lib/ruby_smb/client/negotiation.rb', line 47

def negotiate_response(raw_data)
  response = nil
  if smb1
    packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
    response = packet if packet.valid?
  end
  if smb2 && response.nil?
    packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
    response = packet if packet.valid?
  end
  if response.nil?
    if packet.packet_smb_version == 'SMB1'
      extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
        packet.parameter_block.capabilities.extended_security
      else
        "n/a"
      end
      raise RubySMB::Error::InvalidPacket.new(
        expected_proto:  RubySMB::SMB1::SMB_PROTOCOL_ID,
        expected_cmd:    RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
        expected_custom: "extended_security=1",
        received_proto:  packet.smb_header.protocol,
        received_cmd:    packet.smb_header.command,
        received_custom: "extended_security=#{extended_security}"
      )
    elsif packet.packet_smb_version == 'SMB2'
      raise RubySMB::Error::InvalidPacket.new(
        expected_proto:  RubySMB::SMB2::SMB2_PROTOCOL_ID,
        expected_cmd:    RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
        received_proto:  packet.smb2_header.protocol,
        received_cmd:    packet.smb2_header.command
      )
    else
      raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
    end
  end
  response
end

#parse_negotiate_response(packet) ⇒ String

Sets the supported SMB Protocol and whether or not Signing is enabled based on the Negotiate Response Packet. It also stores the negotiated dialect.

Parameters:

Returns:

  • (String)

    The SMB version as a string ('SMB1', 'SMB2')



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
# File 'lib/ruby_smb/client/negotiation.rb', line 93

def parse_negotiate_response(packet)
  case packet
  when RubySMB::SMB1::Packet::NegotiateResponseExtended
    self.smb1 = true
    self.smb2 = false
    self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
    self.dialect = packet.negotiated_dialect.to_s
    # MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
    # for protocol overhead. Then this value can be used for max read/write size without having to factor in
    # protocol overhead every time.
    self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
    'SMB1'
  when RubySMB::SMB2::Packet::NegotiateResponse
    self.smb1 = false
    self.smb2 = true
    self.signing_required = packet.security_mode.signing_required == 1
    self.dialect = "0x%04x" % packet.dialect_revision
    self.server_max_read_size = packet.max_read_size
    self.server_max_write_size = packet.max_write_size
    self.server_max_transact_size = packet.max_transact_size
    # This value is used in SMB1 only but calculate a valid value anyway
    self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
    'SMB2'
  end

end

#smb1_negotiate_requestRubySMB::SMB1::Packet::NegotiateRequest

Create a SMB1::Packet::NegotiateRequest packet with the dialects filled in based on the protocol options set on the Client.

Returns:



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/ruby_smb/client/negotiation.rb', line 124

def smb1_negotiate_request
  packet = RubySMB::SMB1::Packet::NegotiateRequest.new
  # Default to always enabling Extended Security. It simplifies the Negotiation process
  # while being guaranteed to work with any modern Windows system. We can get more sophisticated
  # with switching this on and off at a later date if the need arises.
  packet.smb_header.flags2.extended_security = 1
  # There is no real good reason to ever send an SMB1 Negotiate packet
  # to Negotiate strictly SMB2, but the protocol WILL support it
  packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
  packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
  packet
end

#smb2_negotiate_requestObject

Create a SMB2::Packet::NegotiateRequest packet with the default dialect added. This will never be used when we may want to communicate over SMB1

@ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet



142
143
144
145
146
147
148
# File 'lib/ruby_smb/client/negotiation.rb', line 142

def smb2_negotiate_request
  packet = RubySMB::SMB2::Packet::NegotiateRequest.new
  packet.security_mode.signing_enabled = 1
  packet.add_dialect(SMB2_DIALECT_DEFAULT)
  packet.client_guid = SecureRandom.random_bytes(16)
  packet
end