Class: RubySMB::Server::ServerClient

Inherits:
Object
  • Object
show all
Includes:
Encryption, Negotiation, SessionSetup, ShareIO, TreeConnect, RubySMB::Signing
Defined in:
lib/ruby_smb/server/server_client.rb,
lib/ruby_smb/server/server_client/share_io.rb,
lib/ruby_smb/server/server_client/encryption.rb,
lib/ruby_smb/server/server_client/negotiation.rb,
lib/ruby_smb/server/server_client/tree_connect.rb,
lib/ruby_smb/server/server_client/session_setup.rb

Overview

This class represents a single connected client to the server. It stores and processes connection specific related information.

Defined Under Namespace

Modules: Encryption, Negotiation, SessionSetup, ShareIO, TreeConnect

Constant Summary collapse

MAX_TREE_CONNECTIONS =
1000

Instance Attribute Summary collapse

Attributes included from RubySMB::Signing

#session_key

Instance Method Summary collapse

Methods included from TreeConnect

#do_tree_connect_smb1, #do_tree_connect_smb2, #do_tree_disconnect_smb1, #do_tree_disconnect_smb2

Methods included from ShareIO

#proxy_share_io_smb1, #proxy_share_io_smb2

Methods included from SessionSetup

#do_logoff_andx_smb1, #do_logoff_smb2, #do_session_setup_andx_smb1, #do_session_setup_smb2

Methods included from Negotiation

#do_negotiate_smb1, #do_negotiate_smb2, #handle_negotiate

Methods included from Encryption

#smb3_decrypt, #smb3_encrypt

Methods included from RubySMB::Signing

#smb1_sign, smb1_sign, #smb2_sign, smb2_sign, #smb3_sign, smb3_sign

Constructor Details

#initialize(server, dispatcher) ⇒ ServerClient

Returns a new instance of ServerClient.

Parameters:

  • server (Server)

    the server that accepted this connection

  • dispatcher (Dispatcher::Socket)

    the connection's socket dispatcher



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ruby_smb/server/server_client.rb', line 26

def initialize(server, dispatcher)
  @server = server
  @dispatcher = dispatcher
  @dialect = nil
  @sequence_counter = 0
  @cipher_id = 0
  @gss_authenticator = server.gss_provider.new_authenticator(self)
  @preauth_integrity_hash_algorithm = nil
  @preauth_integrity_hash_value = nil
  @in_packet_queue = []

  # session id => session instance
  @session_table = {}
end

Instance Attribute Details

#dialectObject (readonly)

Returns the value of attribute dialect.



22
23
24
# File 'lib/ruby_smb/server/server_client.rb', line 22

def dialect
  @dialect
end

#dispatcherObject (readonly)

Returns the value of attribute dispatcher.



22
23
24
# File 'lib/ruby_smb/server/server_client.rb', line 22

def dispatcher
  @dispatcher
end

#session_tableObject (readonly)

Returns the value of attribute session_table.



22
23
24
# File 'lib/ruby_smb/server/server_client.rb', line 22

def session_table
  @session_table
end

Instance Method Details

#disconnect!Object

Disconnect the remote client.



184
185
186
187
# File 'lib/ruby_smb/server/server_client.rb', line 184

def disconnect!
  @dialect = nil
  @dispatcher.tcp_socket.close unless @dispatcher.tcp_socket.closed?
end

#getpeernameString

The peername of the connected socket. This is a combination of the IPv4 or IPv6 address and port number.

Examples:

Parse the value into an IP address

::Socket::unpack_sockaddr_in(server_client.getpeername)

Returns:

  • (String)


56
57
58
# File 'lib/ruby_smb/server/server_client.rb', line 56

def getpeername
  @dispatcher.tcp_socket.getpeername
end

#handle_smb(raw_request) ⇒ Object

Handle a request after the dialect has been negotiated. This is the main handler for all requests after the connection has been established. If a request handler raises NotImplementedError, the server will respond to the client with NT Status STATUS_NOT_SUPPORTED.

Parameters:

  • raw_request (String)

    the request that should be handled



75
76
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/ruby_smb/server/server_client.rb', line 75

def handle_smb(raw_request)
  response = nil

  case raw_request[0...4].unpack1('L>')
  when RubySMB::SMB1::SMB_PROTOCOL_ID
    begin
      header = RubySMB::SMB1::SMBHeader.read(raw_request)
    rescue IOError => e
      logger.error("Caught a #{e.class} while reading the SMB1 header (#{e.message})")
      disconnect!
      return
    end

    begin
      response = handle_smb1(raw_request, header)
    rescue NotImplementedError => e
      message = "Caught a NotImplementedError while handling a #{SMB1::Commands.name(header.command)} request"
      message << " (#{e.message})" if e.message
      logger.error(message)
      response = RubySMB::SMB1::Packet::EmptyPacket.new
      response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
    end

    unless response.nil?
      # set these header fields if they were not initialized
      if response.is_a?(SMB1::Packet::EmptyPacket)
        response.smb_header.command = header.command if response.smb_header.command == 0
        response.smb_header.flags.reply = 1
        nt_status = response.smb_header.nt_status.to_i
        message = "Sending an error packet for SMB1 command: #{SMB1::Commands.name(header.command)}, status: 0x#{nt_status.to_s(16).rjust(8, '0')}"
        if (nt_status_name = WindowsError::NTStatus.find_by_retval(nt_status).first&.name)
          message << " (#{nt_status_name})"
        end
        logger.info(message)
      end

      response.smb_header.pid_high = header.pid_high if response.smb_header.pid_high == 0
      response.smb_header.tid = header.tid if response.smb_header.tid == 0
      response.smb_header.pid_low = header.pid_low if response.smb_header.pid_low == 0
      response.smb_header.uid = header.uid if response.smb_header.uid == 0
      response.smb_header.mid = header.mid if response.smb_header.mid == 0
    end
  when RubySMB::SMB2::SMB2_PROTOCOL_ID
    response = _handle_smb2(raw_request)
  when RubySMB::SMB2::SMB2_TRANSFORM_PROTOCOL_ID
    begin
      header = RubySMB::SMB2::Packet::TransformHeader.read(raw_request)
    rescue IOError => e
      logger.error("Caught a #{e.class} while reading the SMB3 Transform header")
      disconnect!
      return
    end

    begin
      response = handle_smb3_transform(raw_request, header)
    rescue NotImplementedError
      logger.error("Caught a NotImplementedError while handling a SMB3 Transform request")
      response = SMB2::Packet::ErrorPacket.new
      response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
      response.smb2_header.session_id = header.session_id
    end
  end

  if response.nil?
    disconnect!
    return
  end

  send_packet(response)
end

#loggerLogger

The logger object associated with this instance.

Returns:

  • (Logger)


193
194
195
# File 'lib/ruby_smb/server/server_client.rb', line 193

def logger
  @server.logger
end

#metadialectDialect::Definition

The dialects metadata definition.

Returns:



45
46
47
# File 'lib/ruby_smb/server/server_client.rb', line 45

def metadialect
  Dialect::ALL[@dialect]
end

#peerhostObject



60
61
62
# File 'lib/ruby_smb/server/server_client.rb', line 60

def peerhost
  ::Socket::unpack_sockaddr_in(getpeername)[1]
end

#peerportObject



64
65
66
# File 'lib/ruby_smb/server/server_client.rb', line 64

def peerport
  ::Socket::unpack_sockaddr_in(getpeername)[0]
end

#process_gss(buffer = nil) ⇒ Gss::Provider::Result

Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in the negotiation sequence.

Parameters:

  • buffer (String, nil) (defaults to: nil)

    the request GSS request buffer that should be processed

Returns:



152
153
154
# File 'lib/ruby_smb/server/server_client.rb', line 152

def process_gss(buffer=nil)
  @gss_authenticator.process(buffer)
end

#recv_packetString

Receive a single SMB packet from the dispatcher.

Returns:

  • (String)

    the raw packet



201
202
203
204
205
206
207
208
209
210
211
# File 'lib/ruby_smb/server/server_client.rb', line 201

def recv_packet
  return @in_packet_queue.shift if @in_packet_queue.length > 0

  packet = @dispatcher.recv_packet
  if packet && packet.length >= 4 && packet[0...4].unpack1('L>') == RubySMB::SMB2::SMB2_PROTOCOL_ID
    @in_packet_queue += split_smb2_chain(packet)
    packet = @in_packet_queue.shift
  end

  packet
end

#runObject

Run the processing loop to receive and handle requests. This loop runs until an exception occurs or the dispatcher socket is closed.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/ruby_smb/server/server_client.rb', line 160

def run
  loop do
    begin
      raw_request = recv_packet
    rescue RubySMB::Error::CommunicationError
      break
    end

    if @dialect.nil?
      handle_negotiate(raw_request)
      logger.info("Negotiated dialect: #{RubySMB::Dialect[@dialect].full_name}") unless @dialect.nil?
    else
      handle_smb(raw_request)
    end

    break if @dispatcher.tcp_socket.closed?
  end

  disconnect!
end

#send_packet(packet) ⇒ Object

Send a single SMB packet using the dispatcher. If necessary, the packet will be signed.

Parameters:



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/ruby_smb/server/server_client.rb', line 217

def send_packet(packet)
  case metadialect&.family
  when Dialect::FAMILY_SMB1
    session_id = packet.smb_header.uid
  when Dialect::FAMILY_SMB2
    session_id = packet.smb2_header.session_id
  when Dialect::FAMILY_SMB3
    if packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
      session_id = packet.session_id
    else
      session_id = packet.smb2_header.session_id
    end
  end
  session = @session_table[session_id]

  unless session.nil? || session.is_anonymous || session.key.nil? || packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
    case metadialect&.family
    when Dialect::FAMILY_SMB1
      packet = Signing::smb1_sign(packet, session.key, @sequence_counter)
    when Dialect::FAMILY_SMB2
      packet = Signing::smb2_sign(packet, session.key)
    when Dialect::FAMILY_SMB3
      packet = Signing::smb3_sign(packet, session.key, @dialect, @preauth_integrity_hash_value)
    end
  end

  @sequence_counter += 1
  @dispatcher.send_packet(packet)
end

#update_preauth_hash(data) ⇒ Object

Update the preauth integrity hash as used by dialect 3.1.1 for various cryptographic operations. The algorithm and hash values must have been initialized prior to calling this.

Parameters:

  • data (String)

    the data with which to update the preauth integrity hash



252
253
254
255
256
257
258
259
260
261
262
# File 'lib/ruby_smb/server/server_client.rb', line 252

def update_preauth_hash(data)
  unless @preauth_integrity_hash_algorithm
    raise RubySMB::Error::EncryptionError.new(
      'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
    )
  end
  @preauth_integrity_hash_value = OpenSSL::Digest.digest(
    @preauth_integrity_hash_algorithm,
    @preauth_integrity_hash_value + data.to_binary_s
  )
end