# -*- coding: binary -*-

module Msf
  module Exploit::Remote::SMB::Server
    module Share
      module Command
        # @todo Add support to only allow session setup against the configured shared resource
        module SessionSetupAndx

          # Handles an SMB_COM_SESSION_SETUP_ANDX command, used by the client to configure an SMB Session.
          #
          # @param c [Socket] The client sending the request.
          # @param buff [String] The data including the client request.
          # @return [Integer] The number of bytes returned to the client as response.
          def smb_cmd_session_setup_andx(c, buff)
            tree_connect_response = CONST::SMB_TREE_CONN_ANDX_RES_PKT.make_struct
            tree_connect_response.v['WordCount'] = CONST::SMB_TREE_CONN_ANDX_WORD_COUNT
            tree_connect_response.v['AndXCommand'] = CONST::SMB_COM_NO_ANDX_COMMAND
            tree_connect_response.v['AndXReserved'] = 0
            tree_connect_response.v['AndXOffset'] = 0
            tree_connect_response.v['OptionalSupport'] = 1
            tree_connect_response.v['AccessRights'] = TREE_CONNECT_MAX_ACCESS
            tree_connect_response.v['GuestAccessRights'] = 0
            tree_connect_response.v['Payload'] = "A:\x00#{Rex::Text.to_unicode('NTFS')}\x00\x00"

            data = Rex::Text.to_unicode('Unix', 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
              Rex::Text.to_unicode('Samba 3.4.7', 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
              Rex::Text.to_unicode('WORKGROUP', 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature

            send_session_setup_andx_res(c, {
              action: CONST::SMB_SETUP_GUEST,
              data: data,
              andx: CONST::SMB_COM_TREE_CONNECT_ANDX,
              andx_offset: 96,
              andx_command: tree_connect_response
            })
          end

          # Builds and sends an SMB_COM_NT_CREATE_ANDX response.
          #
          # @param c [Socket] The client to answer.
          # @param opts [Hash{Symbol => <Integer, String, Rex::Struct2::CStruct>}] Response custom values.
          # @option opts [Integer] :action SMB Configuration result.
          # @option opts [Integer] :andx_offset The offset in bytes from the start of the SMB Header to the start
          #   of the WordCount field in the next SMBCommand.
          # @option opts [Integer] :reserved Reserved field.
          # @option opts [Integer] :andx The command code for the next SMB Command in the packet.
          # @option opts [String] :data The SMB_Data for the SMB_COM_SESSION_SETUP_ANDX response.
          # @option opts [Rex::Struct2::CStruct] :andx_command The next SMB Command in the packet.
          # @return [Integer] The number of bytes returned to the client as response.
          def send_session_setup_andx_res(c, opts = {})
            action = opts[:action] || 0
            andx_offset = opts[:andx_offset] || 0
            reserved = opts[:reserved] || 0
            andx = opts[:andx] || CONST::SMB_COM_NO_ANDX_COMMAND
            data = opts[:data] || ''
            andx_command = opts[:andx_command] || nil

            pkt = CONST::SMB_SETUP_RES_PKT.make_struct
            smb_set_defaults(c, pkt)

            pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
            pkt['Payload']['SMB'].v['Flags1'] = FLAGS
            pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
            pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT
            pkt['Payload'].v['AndX'] = andx
            pkt['Payload'].v['Reserved1'] = reserved
            pkt['Payload'].v['AndXOffset'] = andx_offset
            pkt['Payload'].v['Action'] = action
            pkt['Payload'].v['Payload'] = data

            if andx_command
              full_pkt = pkt.to_s + andx_command.to_s
              original_length = full_pkt[2, 2].unpack('n')[0]
              original_length = original_length +  andx_command.to_s.length
              full_pkt[2, 2] = [original_length].pack('n')
            else
              full_pkt = pkt.to_s
            end

            c.put(full_pkt)
          end
        end
      end
    end
  end
end
