# -*- coding: binary -*-
module Rex
module Proto
module SMB
class Client

require 'rex/text'
require 'rex/struct2'
require 'rex/proto/smb/constants'
require 'rex/proto/smb/exceptions'
require 'rex/proto/smb/evasions'
require 'rex/proto/smb/utils'
require 'rex/proto/smb/crypt'
require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/utils'


# Some short-hand class aliases
CONST = Rex::Proto::SMB::Constants
CRYPT = Rex::Proto::SMB::Crypt
UTILS = Rex::Proto::SMB::Utils
XCEPT = Rex::Proto::SMB::Exceptions
EVADE = Rex::Proto::SMB::Evasions
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
NTLM_CONST = Rex::Proto::NTLM::Constants
NTLM_UTILS = Rex::Proto::NTLM::Utils

  def initialize(socket)
    self.socket = socket
    self.native_os = 'Windows 2000 2195'
    self.native_lm = 'Windows 2000 5.0'
    self.encrypt_passwords = true
    self.extended_security = false
    self.multiplex_id = rand(0xffff)
    self.process_id = rand(0xffff)
    self.read_timeout = 10
    self.evasion_opts = {

      # Padding is performed between packet headers and data
      'pad_data' => EVADE::EVASION_NONE,

      # File path padding is performed on all open/create calls
      'pad_file' => EVADE::EVASION_NONE,

      # Modify the \PIPE\ string in trans_named_pipe calls
      'obscure_trans_pipe' => EVADE::EVASION_NONE,
    }

    self.verify_signature = false
    self.use_ntlmv2 = false
    self.usentlm2_session = true
    self.send_lm = true
    self.use_lanman_key = false
    self.send_ntlm  = true

    # Signing
    self.sequence_counter = 0
    self.signing_key      = ''
    self.require_signing  = false

    #Misc
    self.spnopt = {}

  end

  # Read a SMB packet from the socket
  def smb_recv

    data = socket.timed_read(4, self.read_timeout)
    if (data.nil? or data.length < 4)
      raise XCEPT::NoReply
    end

    recv_len = data[2,2].unpack('n')[0]
    if (recv_len == 0)
      return data
    end

    recv_len += 4

    while (data.length != recv_len)
      buff = ''

      begin
        buff << self.socket.timed_read(recv_len - data.length, self.read_timeout)
      rescue Timeout::Error
      rescue
        raise XCEPT::ReadPacket
      end

      if (buff.nil? or buff.length == 0)
        raise XCEPT::ReadPacket
      end

      data << buff
    end

    #signing
    if self.require_signing && self.signing_key != ''
      if self.verify_signature
        raise XCEPT::IncorrectSigningError if not CRYPT::is_signature_correct?(self.signing_key,self.sequence_counter,data)
      end
      self.sequence_counter += 1
    end

    return data


  end

  # Send a SMB packet down the socket
  def smb_send(data, evasion_level=0)

    # evasion_level is ignored, since real evasion happens
    # in the actual socket layer

    size = 0
    wait = 0

    #signing
    if self.require_signing && self.signing_key != ''
      data = CRYPT::sign_smb_packet(self.signing_key, self.sequence_counter, data)
      self.sequence_counter += 1
    end

    begin
      # Just send the packet and return
      if (size == 0 or size >= data.length)
        return self.socket.put(data)
      end

      # Break the packet up into chunks and wait between them
      ret = 0
      while ( (chunk = data.slice!(0, size)).length > 0 )
        ret = self.socket.put(chunk)
        if (wait > 0)
          ::IO.select(nil, nil, nil, wait)
        end
      end
      return ret
    end
  end

  # Set the SMB parameters to some reasonable defaults
  def smb_defaults(packet)
    packet.v['MultiplexID'] = self.multiplex_id.to_i
    packet.v['TreeID'] = self.last_tree_id.to_i
    packet.v['UserID'] = self.auth_user_id.to_i
    packet.v['ProcessID'] = self.process_id.to_i
  end

  # Receive a full SMB reply and cache the parsed packet
  def smb_recv_and_cache
    @smb_recv_cache ||= []

    # This will throw an exception if it fails to read the whole packet
    data = self.smb_recv

    pkt = CONST::SMB_BASE_PKT.make_struct
    pkt.from_s(data)

    # Store the received packet into the cache
    @smb_recv_cache << [ pkt, data, Time.now ]
  end

  # Scan the packet receive cache for a matching response
  def smb_recv_cache_find_match(expected_type)
    
    clean = []
    found = nil

    @smb_recv_cache.each do |cent|
      pkt, data, tstamp = cent

      # Return matching packets and mark for removal 
      if pkt['Payload']['SMB'].v['Command'] == expected_type
        found = [pkt,data]
        clean << cent
      end

      # Purge any packets older than 5 minutes
      if Time.now.to_i - tstamp.to_i > 300
        clean << cent
      end

      break if found
    end

    clean.each do |cent|
      @smb_recv_cache.delete(cent)
    end

    found
  end

  # The main dispatcher for all incoming SMB packets
  def smb_recv_parse(expected_type, ignore_errors = false)

    pkt  = nil
    data = nil

    # This allows for some leeway when a previous response has not
    # been processed but a new request was sent. The old response
    # will eventually be timed out of the cache.
    1.upto(3) do |attempt|
      smb_recv_and_cache
      pkt,data = smb_recv_cache_find_match(expected_type)
      break if pkt
    end

    begin
      case pkt['Payload']['SMB'].v['Command']

        when CONST::SMB_COM_NEGOTIATE
          res = smb_parse_negotiate(pkt, data)

        when CONST::SMB_COM_SESSION_SETUP_ANDX
          res = smb_parse_session_setup(pkt, data)

        when CONST::SMB_COM_TREE_CONNECT_ANDX
          res = smb_parse_tree_connect(pkt, data)

        when CONST::SMB_COM_TREE_DISCONNECT
          res = smb_parse_tree_disconnect(pkt, data)

        when CONST::SMB_COM_NT_CREATE_ANDX
          res = smb_parse_create(pkt, data)

        when CONST::SMB_COM_TRANSACTION, CONST::SMB_COM_TRANSACTION2
          res = smb_parse_trans(pkt, data)

        when CONST::SMB_COM_NT_TRANSACT
          res = smb_parse_nttrans(pkt, data)

        when CONST::SMB_COM_NT_TRANSACT_SECONDARY
          res = smb_parse_nttrans(pkt, data)

        when CONST::SMB_COM_OPEN_ANDX
          res = smb_parse_open(pkt, data)

        when CONST::SMB_COM_WRITE_ANDX
          res = smb_parse_write(pkt, data)

        when CONST::SMB_COM_READ_ANDX
          res = smb_parse_read(pkt, data)

        when CONST::SMB_COM_CLOSE
          res = smb_parse_close(pkt, data)

        when CONST::SMB_COM_DELETE
          res = smb_parse_delete(pkt, data)

        else
          raise XCEPT::InvalidCommand
      end

      if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0)
        raise XCEPT::ErrorCode
      end

    rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode
        $!.word_count = pkt['Payload']['SMB'].v['WordCount']
        $!.command = pkt['Payload']['SMB'].v['Command']
        $!.error_code = pkt['Payload']['SMB'].v['ErrorClass']
        raise $!
    end

    return res
  end

  # Process incoming SMB_COM_NEGOTIATE packets
  def smb_parse_negotiate(pkt, data)
    #Process NTLM negotiate responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 17)
      res = CONST::SMB_NEG_RES_NT_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process LANMAN negotiate responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 13)
      res = CONST::SMB_NEG_RES_LM_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process ERROR negotiate responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 1)
      res = CONST::SMB_NEG_RES_ERR_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_SESSION_SETUP_ANDX packets
  def smb_parse_session_setup(pkt, data)
    # Process NTLMSSP negotiate responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 4)
      res = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process LANMAN responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 3)
      res = CONST::SMB_SETUP_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_TREE_CONNECT_ANDX packets
  def smb_parse_tree_connect(pkt, data)

    if (pkt['Payload']['SMB'].v['WordCount'] == 3)
      res = CONST::SMB_TREE_CONN_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_TREE_DISCONNECT packets
  def smb_parse_tree_disconnect(pkt, data)

    # Process SMB responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      res = CONST::SMB_TREE_DISCONN_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_NT_CREATE_ANDX packets
  def smb_parse_create(pkt, data)

    # Windows says 42, but Samba says 34, same structure :-/
    if (pkt['Payload']['SMB'].v['WordCount'] == 42)
      res = CONST::SMB_CREATE_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    if (pkt['Payload']['SMB'].v['WordCount'] == 34)
      res = CONST::SMB_CREATE_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_TRANSACTION packets
  def smb_parse_trans(pkt, data)

    if (pkt['Payload']['SMB'].v['WordCount'] == 10)
      res = CONST::SMB_TRANS_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_NT_TRANSACT packets
  def smb_parse_nttrans(pkt, data)

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    if (pkt['Payload']['SMB'].v['WordCount'] >= 18)
      res = CONST::SMB_NTTRANS_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_OPEN_ANDX packets
  def smb_parse_open(pkt, data)
    # Process open responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 15)
      res = CONST::SMB_OPEN_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_WRITE_ANDX packets
  def smb_parse_write(pkt, data)

    # Process write responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 6)
      res = CONST::SMB_WRITE_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_READ_ANDX packets
  def smb_parse_read(pkt, data)

    # Process read responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 12)
      res = CONST::SMB_READ_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_CLOSE packets
  def smb_parse_close(pkt, data)

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      return pkt
    end

    raise XCEPT::InvalidWordCount
  end

  # Process incoming SMB_COM_DELETE packets
  def smb_parse_delete(pkt, data)

    # Process SMB error responses
    if (pkt['Payload']['SMB'].v['WordCount'] == 0)
      res = CONST::SMB_DELETE_RES_PKT.make_struct
      res.from_s(data)
      return res
    end

    raise XCEPT::InvalidWordCount
  end

  # Request a SMB session over NetBIOS
  def session_request(name = '*SMBSERVER', do_recv = true)

    name ||= '*SMBSERVER'

    data = ''
    data << "\x20" + UTILS.nbname_encode(name) + "\x00"
    data << "\x20" + CONST::NETBIOS_REDIR      + "\x00"

    pkt = CONST::NBRAW_PKT.make_struct
    pkt.v['Type'] = 0x81
    pkt['Payload'].v['Payload'] = data

    # Most SMB implementations can't handle this being fragmented
    ret = self.smb_send(pkt.to_s, EVADE::EVASION_NONE)
    return ret if not do_recv

    res = self.smb_recv

    ack = CONST::NBRAW_PKT.make_struct
    ack.from_s(res)

    if (ack.v['Type'] != 130)
      raise XCEPT::NetbiosSessionFailed
    end

    return ack
  end

  # Negotiate a SMB dialect
  def negotiate(smb_extended_security=true, do_recv = true)

    dialects = ['LANMAN1.0', 'LM1.2X002' ]

    if (self.encrypt_passwords)
      dialects.push('NT LANMAN 1.0', 'NT LM 0.12')
    end

    data = dialects.collect { |dialect| "\x02" + dialect + "\x00" }.join('')

    pkt = CONST::SMB_NEG_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
    pkt['Payload']['SMB'].v['Flags1'] = 0x18

    if(smb_extended_security)
      pkt['Payload']['SMB'].v['Flags2'] = 0x2801
    else
      pkt['Payload']['SMB'].v['Flags2'] = 0xc001
    end

    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s, EVADE::EVASION_NONE)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_NEGOTIATE)

    idx = ack['Payload'].v['Dialect']

    # Check for failed dialect selection
    if (idx < 0 or idx >= dialects.length)
      return nil
    end

    # Set the selected dialect
    self.dialect = dialects[idx]

    # Does the server support extended security negotiation?
    if (ack['Payload'].v['Capabilities'] & 0x80000000 != 0)
      self.extended_security = true
    end

    # Set the security mode
    self.security_mode = ack['Payload'].v['SecurityMode']

    #set require_signing
    if (ack['Payload'].v['SecurityMode'] & 0x08 != 0)
      self.require_signing	= true
    end

    # Set the challenge key
    if (ack['Payload'].v['EncryptionKey'] != nil)
      self.challenge_key = ack['Payload'].v['EncryptionKey']
    else
      # Handle Windows NT 4.0 responses
      if (ack['Payload'].v['KeyLength'] > 0)
        self.challenge_key = ack['Payload'].v['Payload'][0, ack['Payload'].v['KeyLength']]
      end
    end

    # Set the session identifier
    if (ack['Payload'].v['SessionKey'] != nil)
      self.session_id = ack['Payload'].v['SessionKey']
    end

    # Extract the payload (GUID/SecurityBlob)
    buf = ack['Payload'].v['Payload'] || ''

    # Set the server GUID
    if (self.extended_security and buf.length >= 16)
      self.server_guid = buf[0,16]
    end

    # Set the server SecurityBlob
    if (self.extended_security and buf.length > 16)
      # buf[16, buf.length - 16]
    end

    # The number of 100-nanosecond intervals that have elapsed since January 1, 1601, in
    # Coordinated Universal Time (UTC) format.
    # We convert it to a friendly Time object here
    self.system_time = UTILS.time_smb_to_unix(ack['Payload'].v['SystemTimeHigh'],ack['Payload'].v['SystemTimeLow'])
    self.system_time = ::Time.at( self.system_time )

    # A signed 16-bit signed integer that represents the server's time zone, in minutes,
    # from UTC. The time zone of the server MUST be expressed in minutes, plus or minus,
    # from UTC.
    # NOTE: althought the spec says +/- it doesn't say that it should be inverted :-/
    system_zone = ack['Payload'].v['ServerTimeZone']
    # Convert the ServerTimeZone to _seconds_ and back into a signed integer :-/
    if (system_zone & 0x8000) == 0x8000
      system_zone = (( (~system_zone)  & 0x0FFF ) + 1 )
    else
      system_zone *= -1
    end
    self.system_zone = system_zone * 60

    return ack
  end


  # Authenticate and establish a session
  def session_setup(*args)

    if (self.dialect =~ /^(NT LANMAN 1.0|NT LM 0.12)$/)

      if (self.challenge_key)
        return self.session_setup_no_ntlmssp(*args)
      end

      if ( self.extended_security )
        return self.session_setup_with_ntlmssp(*args)
      end

    end

    return self.session_setup_clear(*args)
  end

  # Authenticate using clear-text passwords
  def session_setup_clear(user = '', pass = '', domain = '', do_recv = true)

    data = [ pass, user, domain, self.native_os, self.native_lm ].collect{ |a| a + "\x00" }.join('');

    pkt = CONST::SMB_SETUP_LANMAN_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 10
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['PasswordLen'] = pass.length + 1
    pkt['Payload'].v['Capabilities'] = 64
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

    if (ack['Payload'].v['Action'] != 1 and user.length > 0)
      self.auth_user = user
    end

    self.auth_user_id = ack['Payload']['SMB'].v['UserID']

    info = ack['Payload'].v['Payload'].split(/\x00/n)
    self.peer_native_os = info[0]
    self.peer_native_lm = info[1]
    self.default_domain = info[2]

    return ack
  end

  # Authenticate without NTLMSSP
  def session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true)

    # Requires a challenge key to have been seen during negotiation
    raise XCEPT::NTLM1MissingChallenge if not self.challenge_key

    #
    # We can not yet handle signing in this situation
    # But instead of throwing an exception,we will disable signing, continue and hope for the best.
    #

    #raise XCEPT::SigningError if self.require_signing
    self.require_signing = false if self.require_signing


    if NTLM_UTILS.is_pass_ntlm_hash?(pass)
      arglm = {
        :lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
        :challenge =>  self.challenge_key
      }
      hash_lm = NTLM_CRYPT::lm_response(arglm)

      argntlm = {
        :ntlm_hash =>  [ pass.upcase()[33,65] ].pack('H32'),
        :challenge =>  self.challenge_key
      }
      hash_nt = NTLM_CRYPT::ntlm_response(argntlm)
    else
      hash_lm = pass.length > 0 ? NTLM_CRYPT.lanman_des(pass, self.challenge_key) : ''
      hash_nt = pass.length > 0 ? NTLM_CRYPT.ntlm_md4(pass, self.challenge_key)   : ''
    end

    data = ''
    data << hash_lm
    data << hash_nt
    data << user + "\x00"
    data << domain + "\x00"
    data << self.native_os + "\x00"
    data << self.native_lm + "\x00"

    pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    pkt['Payload']['SMB'].v['Flags2'] = 0x2001
    pkt['Payload']['SMB'].v['WordCount'] = 13
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
    pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
    pkt['Payload'].v['Capabilities'] = 64
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

    if (ack['Payload'].v['Action'] != 1 and user.length > 0)
      self.auth_user = user
    end

    self.auth_user_id = ack['Payload']['SMB'].v['UserID']

    info = ack['Payload'].v['Payload'].split(/\x00/n)

    self.peer_native_os = info[0]
    self.peer_native_lm = info[1]
    self.default_domain = info[2]

    return ack
  end


  # Authenticate without ntlmssp with a precomputed hash pair
  def session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt, do_recv = true)

    data = ''
    data << hash_lm
    data << hash_nt
    data << user + "\x00"
    data << domain + "\x00"
    data << self.native_os + "\x00"
    data << self.native_lm + "\x00"

    pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    pkt['Payload']['SMB'].v['Flags2'] = 0x2001
    pkt['Payload']['SMB'].v['WordCount'] = 13
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
    pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
    pkt['Payload'].v['Capabilities'] = 64
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

    if (ack['Payload'].v['Action'] != 1 and user.length > 0)
      self.auth_user = user
    end

    self.auth_user_id = ack['Payload']['SMB'].v['UserID']

    info = ack['Payload'].v['Payload'].split(/\x00/n)

    self.peer_native_os = info[0]
    self.peer_native_lm = info[1]
    self.default_domain = info[2]

    return ack
  end

  # Authenticate using extended security negotiation
  def session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do_recv = true)

    ntlm_options = {
        :signing 		=> self.require_signing,
        :usentlm2_session 	=> self.usentlm2_session,
        :use_ntlmv2 		=> self.use_ntlmv2,
        :send_lm 		=> self.send_lm,
        :send_ntlm		=> self.send_ntlm,
        :use_lanman_key		=> self.use_lanman_key
        }

    ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)

    if (name == nil)
      name = Rex::Text.rand_text_alphanumeric(16)
    end

    blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name, ntlmssp_flags)

    native_data = ''
    native_data << self.native_os + "\x00"
    native_data << self.native_lm + "\x00"

    pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end
    pkt['Payload']['SMB'].v['WordCount'] = 12
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['SecurityBlobLen'] = blob.length
    pkt['Payload'].v['Capabilities'] = 0x800000d4
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = blob + native_data

    ret = self.smb_send(pkt.to_s)

    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)


    # The server doesn't know about NTLM_NEGOTIATE
    if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
      return session_setup_no_ntlmssp(user, pass, domain)
    end

    # Make sure the error code tells us to continue processing
    if (ack['Payload']['SMB'].v['ErrorClass'] != 0xc0000016)
      failure = XCEPT::ErrorCode.new
      failure.word_count = ack['Payload']['SMB'].v['WordCount']
      failure.command = ack['Payload']['SMB'].v['Command']
      failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
      raise failure
    end

    # Extract the SecurityBlob from the response
    data = ack['Payload'].v['Payload']
    blob = data.slice!(0, ack['Payload'].v['SecurityBlobLen'])

    # Extract the native lanman and os strings
    info = data.split(/\x00/n)
    self.peer_native_os = info[0]
    self.peer_native_lm = info[1]

    # Save the temporary UserID for use in the next request
    temp_user_id = ack['Payload']['SMB'].v['UserID']

    # Get default data
    blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(blob)
    self.challenge_key = blob_data[:challenge_key]
    server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
    #netbios name
    self.default_name =  blob_data[:default_name] || ''
    #netbios domain
    self.default_domain = blob_data[:default_domain] || ''
    #dns name
    self.dns_host_name =  blob_data[:dns_host_name] || ''
    #dns domain
    self.dns_domain_name =  blob_data[:dns_domain_name] || ''
    #Client time
    chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''


    resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, self.challenge_key, domain,
                        default_name, default_domain, dns_host_name,
                        dns_domain_name, chall_MsvAvTimestamp ,
                        self.spnopt, ntlm_options)
    enc_session_key = ''
    self.sequence_counter = 0

    if self.require_signing
      self.signing_key, enc_session_key, ntlmssp_flags = NTLM_UTILS.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain,
                      self.challenge_key, client_challenge, ntlm_cli_challenge,
                      ntlm_options)
    end

    # Create the security blob data
    blob = NTLM_UTILS.make_ntlmssp_secblob_auth(domain, name, user, resp_lm, resp_ntlm, enc_session_key, ntlmssp_flags)

    pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end
    pkt['Payload']['SMB'].v['WordCount'] = 12
    pkt['Payload']['SMB'].v['UserID'] = temp_user_id
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['Capabilities'] = 0x8000d05c
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['SecurityBlobLen'] = blob.length
    pkt['Payload'].v['Payload'] = blob + native_data

    # NOTE: if do_recv is set to false, we cant reach here...
    self.smb_send(pkt.to_s)

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)

    # Make sure that authentication succeeded
    if (ack['Payload']['SMB'].v['ErrorClass'] != 0)

      if (user.length == 0)
        # Ensure that signing is disabled when we hit this corner case
        self.require_signing = false

        # Fall back to the non-ntlmssp authentication method
        return self.session_setup_no_ntlmssp(user, pass, domain)
      end

      failure = XCEPT::ErrorCode.new
      failure.word_count = ack['Payload']['SMB'].v['WordCount']
      failure.command = ack['Payload']['SMB'].v['Command']
      failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
      raise failure
    end

    self.auth_user_id = ack['Payload']['SMB'].v['UserID']

    if (ack['Payload'].v['Action'] != 1 and user.length > 0)
      self.auth_user = user
    end

    return ack
  end


  # An exploit helper function for sending arbitrary SPNEGO blobs
  def session_setup_with_ntlmssp_blob(blob = '', do_recv = true, userid = 0)
    native_data = ''
    native_data << self.native_os + "\x00"
    native_data << self.native_lm + "\x00"

    pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    pkt['Payload']['SMB'].v['Flags2'] = 0x2801
    pkt['Payload']['SMB'].v['WordCount'] = 12
    pkt['Payload']['SMB'].v['UserID'] = userid
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['SecurityBlobLen'] = blob.length
    pkt['Payload'].v['Capabilities'] = 0x8000d05c
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = blob + native_data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, false)
  end


  # Authenticate using extended security negotiation (NTLMSSP), but stop half-way, using the temporary ID
  def session_setup_with_ntlmssp_temp(domain = '', name = nil, do_recv = true)

    if (name == nil)
      name = Rex::Text.rand_text_alphanumeric(16)
    end

    blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name)

    native_data = ''
    native_data << self.native_os + "\x00"
    native_data << self.native_lm + "\x00"

    pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    pkt['Payload']['SMB'].v['Flags2'] = 0x2801
    pkt['Payload']['SMB'].v['WordCount'] = 12
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['MaxBuff'] = 0xffdf
    pkt['Payload'].v['MaxMPX'] = 2
    pkt['Payload'].v['VCNum'] = 1
    pkt['Payload'].v['SecurityBlobLen'] = blob.length
    pkt['Payload'].v['Capabilities'] = 0x8000d05c
    pkt['Payload'].v['SessionKey'] = self.session_id
    pkt['Payload'].v['Payload'] = blob + native_data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)

    # The server doesn't know about NTLM_NEGOTIATE, try ntlmv1
    if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
      return session_setup_no_ntlmssp(user, pass, domain)
    end

    # Make sure the error code tells us to continue processing
    if (ack['Payload']['SMB'].v['ErrorClass'] != 0xc0000016)
      failure = XCEPT::ErrorCode.new
      failure.word_count = ack['Payload']['SMB'].v['WordCount']
      failure.command = ack['Payload']['SMB'].v['Command']
      failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
      raise failure
    end

    # Extract the SecurityBlob from the response
    data = ack['Payload'].v['Payload']
    blob = data.slice!(0, ack['Payload'].v['SecurityBlobLen'])

    # Extract the native lanman and os strings
    info = data.split(/\x00/n)
    self.peer_native_os = info[0]
    self.peer_native_lm = info[1]

    # Save the temporary UserID for use in the next request
    self.auth_user_id = ack['Payload']['SMB'].v['UserID']

    # Extract the NTLM challenge key the lazy way
    cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00")

    if (cidx == -1)
      raise XCEPT::NTLM2MissingChallenge
    end

    # Store the challenge key
    self.challenge_key = blob[cidx + 24, 8]

    return ack
  end

  # Connect to a specified share with an optional password
  def tree_connect(share = 'IPC$', pass = '', do_recv = true)

    data = [ pass, share, '?????' ].collect{ |a| a + "\x00" }.join('');

    pkt = CONST::SMB_TREE_CONN_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])
    pkt['Payload']['SMB'].v['TreeID'] = 0

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 4
    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['PasswordLen'] = pass.length + 1
    pkt['Payload'].v['Capabilities'] = 64
    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_TREE_CONNECT_ANDX)

    self.last_tree_id = ack['Payload']['SMB'].v['TreeID']
    # why bother?
    # info = ack['Payload'].v['Payload'].split(/\x00/)

    return ack
  end

  # Disconnect from the current tree
  def tree_disconnect(tree_id = self.last_tree_id, do_recv = true)

    pkt = CONST::SMB_TREE_DISCONN_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_DISCONNECT
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 0
    pkt['Payload']['SMB'].v['TreeID'] = tree_id

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_TREE_DISCONNECT)

    if (tree_id == self.last_tree_id)
      self.last_tree_id = 0
    end

    return ack
  end

  # Returns a SMB_CREATE_RES response for a given named pipe
  def create_pipe(filename, disposition = 1, impersonation = 2)
    self.create(filename)
  end

  # Creates a file or opens an existing pipe
  def create(filename, disposition = 1, impersonation = 2, do_recv = true)

    pkt = CONST::SMB_CREATE_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 24

    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['FileNameLen'] = filename.length
    pkt['Payload'].v['CreateFlags'] = 0x16
    pkt['Payload'].v['AccessMask'] = 0x02000000 # Maximum Allowed
    pkt['Payload'].v['ShareAccess'] = 7
    pkt['Payload'].v['CreateOptions'] = 0
    pkt['Payload'].v['Impersonation'] = impersonation
    pkt['Payload'].v['Disposition'] = disposition
    pkt['Payload'].v['Payload'] = filename + "\x00"

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_NT_CREATE_ANDX)

    # Save off the FileID
    if (ack['Payload'].v['FileID'] > 0)
      self.last_file_id = ack['Payload'].v['FileID']
    end

    return ack
  end

  # Deletes a file from a share
  def delete(filename, tree_id = self.last_tree_id, do_recv = true)

    pkt = CONST::SMB_DELETE_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_DELETE
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['TreeID'] = tree_id
    pkt['Payload']['SMB'].v['WordCount'] = 1

    pkt['Payload'].v['SearchAttributes'] = 0x06
    pkt['Payload'].v['BufferFormat'] = 4
    pkt['Payload'].v['Payload'] = filename + "\x00"

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_DELETE)

    return ack
  end

  # Opens an existing file or creates a new one
  def open(filename, mode = 0x12, access = 0x42, do_recv = true)

    pkt = CONST::SMB_OPEN_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_OPEN_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 15

    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['Access'] = access
    pkt['Payload'].v['SearchAttributes'] = 0x06
    pkt['Payload'].v['OpenFunction'] = mode
    pkt['Payload'].v['Payload'] = filename + "\x00"

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_OPEN_ANDX)

    # Save off the FileID
    if (ack['Payload'].v['FileID'] > 0)
      self.last_file_id = ack['Payload'].v['FileID']
    end

    return ack
  end

  # Closes an open file handle
  def close(file_id = self.last_file_id, tree_id = self.last_tree_id, do_recv = true)

    pkt = CONST::SMB_CLOSE_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['TreeID'] = tree_id
    pkt['Payload']['SMB'].v['WordCount'] = 3

    pkt['Payload'].v['FileID'] = file_id
    pkt['Payload'].v['LastWrite'] = -1

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_CLOSE)

    return ack
  end

  # Writes data to an open file handle
  def write(file_id = self.last_file_id, offset = 0, data = '', do_recv = true)
    pkt = CONST::SMB_WRITE_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    data_offset = pkt.to_s.length - 4

    filler = EVADE.make_offset_filler(evasion_opts['pad_data'], 4096 - data.length - data_offset)

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_WRITE_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2805
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 14

    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['FileID'] = file_id
    pkt['Payload'].v['Offset'] = offset
    pkt['Payload'].v['Reserved2'] = -1
    pkt['Payload'].v['WriteMode'] = 8
    pkt['Payload'].v['Remaining'] = data.length
    # pkt['Payload'].v['DataLenHigh'] = (data.length / 65536).to_i
    pkt['Payload'].v['DataLenLow'] = (data.length % 65536).to_i
    pkt['Payload'].v['DataOffset'] = data_offset + filler.length
    pkt['Payload'].v['Payload'] = filler + data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_WRITE_ANDX)

    return ack
  end


  # Reads data from an open file handle
  def read(file_id = self.last_file_id, offset = 0, data_length = 64000, do_recv = true)

    pkt = CONST::SMB_READ_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 10

    pkt['Payload'].v['AndX'] = 255
    pkt['Payload'].v['FileID'] = file_id
    pkt['Payload'].v['Offset'] = offset
    # pkt['Payload'].v['MaxCountHigh'] = (data_length / 65536).to_i
    pkt['Payload'].v['MaxCountLow'] = (data_length % 65536).to_i
    pkt['Payload'].v['MinCount'] = data_length
    pkt['Payload'].v['Reserved2'] = -1

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_READ_ANDX, true)

    err = ack['Payload']['SMB'].v['ErrorClass']

    # Catch some non-fatal error codes
    if (err != 0 && err != CONST::SMB_ERROR_BUFFER_OVERFLOW)
      failure = XCEPT::ErrorCode.new
      failure.word_count = ack['Payload']['SMB'].v['WordCount']
      failure.command = ack['Payload']['SMB'].v['Command']
      failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
      raise failure
    end

    return ack
  end


  # Perform a transaction against a named pipe
  def trans_named_pipe(file_id, data = '', no_response = nil)
    pipe = EVADE.make_trans_named_pipe_name(evasion_opts['pad_file'])
    self.trans(pipe, '', data, 2, [0x26, file_id].pack('vv'), no_response)
  end

  # Perform a mailslot write over SMB
  # Warning: This can kill srv.sys unless MS06-035 is applied
  def trans_mailslot (name, data = '')
    # Setup data must be:
    #  Operation: 1 (write)
    #   Priority: 0
    #      Class: Reliable
    self.trans_maxzero(name, '', data, 3, [1, 0, 1].pack('vvv'), true )
  end

  # Perform a transaction against a given pipe name
  def trans(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

    # Null-terminate the pipe parameter if needed
    if (pipe[-1,1] != "\x00")
      pipe << "\x00"
    end

    pkt = CONST::SMB_TRANS_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
    mlen = 4200

    # Figure out how much space is taken up by our current arguments
    xlen =  pipe.length + param.length + body.length

    filler1 = ''
    filler2 = ''

    # Fill any available space depending on the evasion settings
    if (xlen < mlen)
      filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
      filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    end

    # Squish the whole thing together
    data = pipe + filler1 + param + filler2 + body

    # Throw some form of a warning out?
    if (data.length > mlen)
      # XXX This call will more than likely fail :-(
    end

    # Calculate all of the offsets
    base_offset = pkt.to_s.length + (setup_count * 2) - 4
    param_offset = base_offset + pipe.length + filler1.length
    data_offset = param_offset + filler2.length + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCountMax'] = 1024
    pkt['Payload'].v['DataCountMax'] = 65000
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset
    pkt['Payload'].v['SetupCount'] = setup_count
    pkt['Payload'].v['SetupData'] = setup_data

    pkt['Payload'].v['Payload'] = data

    if no_response
      pkt['Payload'].v['Flags'] = 2
    end

    ret = self.smb_send(pkt.to_s)
    return ret if no_response or not do_recv

    self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
  end



  # Perform a transaction against a given pipe name
  # Difference from trans: sets MaxParam/MaxData to zero
  # This is required to trigger mailslot bug :-(
  def trans_maxzero(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

    # Null-terminate the pipe parameter if needed
    if (pipe[-1] != 0)
      pipe << "\x00"
    end

    pkt = CONST::SMB_TRANS_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
    mlen = 4200

    # Figure out how much space is taken up by our current arguments
    xlen =  pipe.length + param.length + body.length

    filler1 = ''
    filler2 = ''

    # Fill any available space depending on the evasion settings
    if (xlen < mlen)
      filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
      filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    end

    # Squish the whole thing together
    data = pipe + filler1 + param + filler2 + body

    # Throw some form of a warning out?
    if (data.length > mlen)
      # XXX This call will more than likely fail :-(
    end

    # Calculate all of the offsets
    base_offset = pkt.to_s.length + (setup_count * 2) - 4
    param_offset = base_offset + pipe.length + filler1.length
    data_offset = param_offset + filler2.length + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCountMax'] = 0
    pkt['Payload'].v['DataCountMax'] = 0
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset
    pkt['Payload'].v['SetupCount'] = setup_count
    pkt['Payload'].v['SetupData'] = setup_data

    pkt['Payload'].v['Payload'] = data

    if no_response
      pkt['Payload'].v['Flags'] = 2
    end

    ret = self.smb_send(pkt.to_s)
    return ret if no_response or not do_recv

    self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
  end


  # Perform a transaction against a given pipe name (no null terminator)
  def trans_nonull(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

    pkt = CONST::SMB_TRANS_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
    mlen = 4200

    # Figure out how much space is taken up by our current arguments
    xlen =  pipe.length + param.length + body.length

    filler1 = ''
    filler2 = ''

    # Fill any available space depending on the evasion settings
    if (xlen < mlen)
      filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
      filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    end

    # Squish the whole thing together
    data = pipe + filler1 + param + filler2 + body

    # Throw some form of a warning out?
    if (data.length > mlen)
      # XXX This call will more than likely fail :-(
    end

    # Calculate all of the offsets
    base_offset = pkt.to_s.length + (setup_count * 2) - 4
    param_offset = base_offset + pipe.length + filler1.length
    data_offset = param_offset + filler2.length + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCountMax'] = 0
    pkt['Payload'].v['DataCountMax'] = 0
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset
    pkt['Payload'].v['SetupCount'] = setup_count
    pkt['Payload'].v['SetupData'] = setup_data

    pkt['Payload'].v['Payload'] = data

    if no_response
      pkt['Payload'].v['Flags'] = 2
    end

    ret = self.smb_send(pkt.to_s)
    return ret if no_response or not do_recv

    self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
  end

  # Perform a transaction2 request using the specified subcommand, parameters, and data
  def trans2(subcommand, param = '', body = '', do_recv = true)

    setup_count = 1
    setup_data = [subcommand].pack('v')

    data = param + body

    pkt = CONST::SMB_TRANS2_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    base_offset = pkt.to_s.length + (setup_count * 2) - 4
    param_offset = base_offset
    data_offset = param_offset + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCountMax'] = 1024
    pkt['Payload'].v['DataCountMax'] = 65000
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset
    pkt['Payload'].v['SetupCount'] = setup_count
    pkt['Payload'].v['SetupData'] = setup_data

    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_TRANSACTION2)

    return ack
  end


  # Perform a nttransaction request using the specified subcommand, parameters, and data
  def nttrans(subcommand, param = '', body = '', setup_count = 0, setup_data = '', do_recv = true)

    data = param + body

    pkt = CONST::SMB_NTTRANS_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    base_offset = pkt.to_s.length + (setup_count * 2) - 4
    param_offset = base_offset
    data_offset = param_offset + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 19 + setup_count

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCountMax'] = 1024
    pkt['Payload'].v['DataCountMax'] = 65000
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset
    pkt['Payload'].v['SetupCount'] = setup_count
    pkt['Payload'].v['SetupData'] = setup_data
    pkt['Payload'].v['Subcommand'] = subcommand

    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT)
    return ack
  end

  # Perform a nttransaction request using the specified subcommand, parameters, and data
  def nttrans_secondary(param = '', body = '', do_recv = true)

    data = param + body

    pkt = CONST::SMB_NTTRANS_SECONDARY_PKT.make_struct
    self.smb_defaults(pkt['Payload']['SMB'])

    base_offset = pkt.to_s.length - 4
    param_offset = base_offset
    data_offset = param_offset + param.length

    pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT_SECONDARY
    pkt['Payload']['SMB'].v['Flags1'] = 0x18
    if self.require_signing
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] = 0x2807
    else
      #ascii
      pkt['Payload']['SMB'].v['Flags2'] =  0x2801
    end

    pkt['Payload']['SMB'].v['WordCount'] = 18

    pkt['Payload'].v['ParamCountTotal'] = param.length
    pkt['Payload'].v['DataCountTotal'] = body.length
    pkt['Payload'].v['ParamCount'] = param.length
    pkt['Payload'].v['ParamOffset'] = param_offset
    pkt['Payload'].v['DataCount'] = body.length
    pkt['Payload'].v['DataOffset'] = data_offset

    pkt['Payload'].v['Payload'] = data

    ret = self.smb_send(pkt.to_s)
    return ret if not do_recv

    ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT_SECONDARY)
    return ack
  end

  def queryfs(level)
    parm = [level].pack('v')

    begin
      resp = trans2(CONST::TRANS2_QUERY_FS_INFO, parm, '')

      pcnt = resp['Payload'].v['ParamCount']
      dcnt = resp['Payload'].v['DataCount']
      poff = resp['Payload'].v['ParamOffset']
      doff = resp['Payload'].v['DataOffset']

      # Get the raw packet bytes
      resp_rpkt = resp.to_s

      # Remove the NetBIOS header
      resp_rpkt.slice!(0, 4)

      resp_parm = resp_rpkt[poff, pcnt]
      resp_data = resp_rpkt[doff, dcnt]
      return resp_data

    rescue ::Exception
      raise $!
    end
  end

  def symlink(src,dst)
    parm = [513, 0x00000000].pack('vV') + src + "\x00"

    begin
      resp = trans2(CONST::TRANS2_SET_PATH_INFO, parm, dst + "\x00")

      pcnt = resp['Payload'].v['ParamCount']
      dcnt = resp['Payload'].v['DataCount']
      poff = resp['Payload'].v['ParamOffset']
      doff = resp['Payload'].v['DataOffset']

      # Get the raw packet bytes
      resp_rpkt = resp.to_s

      # Remove the NetBIOS header
      resp_rpkt.slice!(0, 4)

      resp_parm = resp_rpkt[poff, pcnt]
      resp_data = resp_rpkt[doff, dcnt]
      return resp_data

    rescue ::Exception
      raise $!
    end
  end

  # Obtains allocation information on the mounted tree
  def queryfs_info_allocation
    data = queryfs(CONST::SMB_INFO_ALLOCATION)
    head = %w{fs_id sectors_per_unit unit_total units_available bytes_per_sector}
    vals = data.unpack('VVVVv')
    info = { }
    head.each_index {|i| info[head[i]]=vals[i]}
    return info
  end

  # Obtains volume information on the mounted tree
  def queryfs_info_volume
    data = queryfs(CONST::SMB_INFO_VOLUME)
    vals = data.unpack('VCA*')
    return {
      'serial' => vals[0],
      'label'  => vals[2][0,vals[1]].gsub("\x00", '')
    }
  end

  # Obtains file system volume information on the mounted tree
  def queryfs_fs_volume
    data = queryfs(CONST::SMB_QUERY_FS_VOLUME_INFO)
    vals = data.unpack('VVVVCCA*')
    return {
      'create_time' => (vals[1] << 32) + vals[0],
      'serial'      => vals[2],
      'label'       => vals[6][0,vals[3]].gsub("\x00", '')
    }
  end

  # Obtains file system size information on the mounted tree
  def queryfs_fs_size
    data = queryfs(CONST::SMB_QUERY_FS_SIZE_INFO)
    vals = data.unpack('VVVVVV')
    return {
      'total_alloc_units' => (vals[1] << 32) + vals[0],
      'total_free_units'  => (vals[3] << 32) + vals[2],
      'sectors_per_unit'  => vals[4],
      'bytes_per_sector'  => vals[5]
    }
  end

  # Obtains file system device information on the mounted tree
  def queryfs_fs_device
    data = queryfs(CONST::SMB_QUERY_FS_DEVICE_INFO)
    vals = data.unpack('VV')
    return {
      'device_type'   => vals[0],
      'device_chars'  => vals[1],
    }
  end

  # Obtains file system attribute information on the mounted tree
  def queryfs_fs_attribute
    data = queryfs(CONST::SMB_QUERY_FS_ATTRIBUTE_INFO)
    vals = data.unpack('VVVA*')
    return {
      'fs_attributes' => vals[0],
      'max_file_name' => vals[1],
      'fs_name'       => vals[3][0, vals[2]].gsub("\x00", '')
    }
  end

  # Enumerates a specific path on the mounted tree
  def find_first(path)
    files = { }
    parm = [
      26,  # Search for ALL files
      20,  # Maximum search count
      6,   # Resume and Close on End of Search
      260, # Level of interest
      0,   # Storage type is zero
    ].pack('vvvvV') + path + "\x00"

    resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '')
    search_next = 0

    # Loop until we run out of results
    loop do
      pcnt = resp['Payload'].v['ParamCount']
      dcnt = resp['Payload'].v['DataCount']
      poff = resp['Payload'].v['ParamOffset']
      doff = resp['Payload'].v['DataOffset']

      # Get the raw packet bytes
      resp_rpkt = resp.to_s

      # Remove the NetBIOS header
      resp_rpkt.slice!(0, 4)

      resp_parm = resp_rpkt[poff, pcnt]
      resp_data = resp_rpkt[doff, dcnt]

      if search_next == 0
        # search id, search count, end of search, error offset, last name offset
        sid, scnt, eos, eoff, loff = resp_parm.unpack('v5')
      else
        # FIND_NEXT doesn't return a SID
        scnt, eos, eoff, loff = resp_parm.unpack('v4')
      end

      didx = 0
      while (didx < resp_data.length)
        info_buff = resp_data[didx, 70]
        break if info_buff.length != 70

        info = info_buff.unpack(
          'V'+	# Next Entry Offset
          'V'+	# File Index
          'VV'+	# Time Create
          'VV'+	# Time Last Access
          'VV'+	# Time Last Write
          'VV'+	# Time Change
          'VV'+	# End of File
          'VV'+	# Allocation Size
          'V'+	# File Attributes
          'V'+	# File Name Length
          'V'+	# Extended Attr List Length
          'C'+	# Short File Name Length
          'C' 	# Reserved
        )

        name = resp_data[didx + 70 + 24, info[15]]

        # Verify that the filename was actually present
        break unless name

        # Key the file list minus any trailing nulls
        files[name.sub(/\x00+$/n, '')] =
        {
          'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D',
          'attr' => info[14],
          'info' => info
        }

        break if info[0] == 0
        didx += info[0]
      end

      last_search_id = sid
      last_offset    = loff
      last_filename  = name

      # Exit the search if we reached the end of our results
      break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0)

      # If we aren't at the end of the search, run find_next
      resp = find_next(last_search_id, last_offset, last_filename)

      # Flip bit so response params will parse correctly
      search_next = 1 
    end

    files
  end

  # Supplements find_first if file/dir count exceeds max search count
  def find_next(sid, resume_key, last_filename)

    parm = [
      sid,                # Search ID
      20,                 # Maximum search count (Size of 20 keeps response to 1 packet)
      260,                # Level of interest
      resume_key,         # Resume key from previous (Last name offset)
      6,                  # Close search if end of search
    ].pack('vvvVv') + 
    last_filename.to_s +  # Last filename returned from find_first or find_next
    "\x00"                # Terminate the file name

    # Returns the FIND_NEXT2 response packet for parsing by the find_first function
    trans2(CONST::TRANS2_FIND_NEXT2, parm, '')
  end

  # Recursively search for files matching a regular expression
  def file_search(current_path, regex, depth)
    depth -= 1
    return [] if depth < 0

    results = find_first(current_path + "*")
    files = []

    results.each_pair do |fname, finfo|

      # Skip current and parent directory results
      next if %W{. ..}.include?(fname)

      # Verify the results contain an attribute
      next unless finfo and finfo['attr']

      if finfo['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0
        # Add any matching files to our result set
        files << "#{current_path}#{fname}" if fname =~ regex
      else
        # Recurse into the discovery subdirectory for more files
        begin
          search_path = "#{current_path}#{fname}\\"
          file_search(search_path, regex, depth).each {|fn| files << fn }
        rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
          
          # Ignore common errors related to permissions and non-files
          if %W{
            STATUS_ACCESS_DENIED
            STATUS_NO_SUCH_FILE
            STATUS_OBJECT_NAME_NOT_FOUND
            STATUS_OBJECT_PATH_NOT_FOUND
            }.include? e.get_error(e.error_code)
            next
          end

          $stderr.puts [e, e.get_error(e.error_code), search_path]

          raise e
        end
      end

    end

    files.uniq
  end

  # Creates a new directory on the mounted tree
  def create_directory(name)
    files = { }
    parm = [0].pack('V') + name + "\x00"
    resp = trans2(CONST::TRANS2_CREATE_DIRECTORY, parm, '')
  end

# public read/write methods
  attr_accessor	:native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts
  attr_accessor	:verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm
  attr_accessor :system_time, :system_zone
  attr_accessor :spnopt

# public read methods
  attr_reader		:dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
  attr_reader		:default_domain, :default_name, :auth_user, :auth_user_id
  attr_reader		:multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
  attr_reader		:dns_host_name, :dns_domain_name
  attr_reader		:security_mode, :server_guid
  attr_reader		:sequence_counter,:signing_key, :require_signing

# private write methods
  attr_writer		:dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
  attr_writer		:default_domain, :default_name, :auth_user, :auth_user_id
  attr_writer		:dns_host_name, :dns_domain_name
  attr_writer		:multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
  attr_writer		:security_mode, :server_guid
  attr_writer		:sequence_counter,:signing_key, :require_signing

  attr_accessor	:socket

end
end
end
end
