# -*- coding: binary -*-
require 'rex/post/meterpreter'
require 'rex/post/meterpreter/extensions/sniffer/command_ids'

module Rex
module Post
module Meterpreter
module Ui

###
#
# Packet sniffer extension user interface.
#
###
class Console::CommandDispatcher::Sniffer

  Klass = Console::CommandDispatcher::Sniffer

  include Console::CommandDispatcher
  include Rex::Post::Meterpreter::Extensions::Sniffer

  #
  # Initializes an instance of the sniffer command interaction.
  #
  def initialize(shell)
    super
  end

  #
  # List of supported commands.
  #
  def commands
    {
      'sniffer_interfaces' => 'Enumerate all sniffable network interfaces',
      'sniffer_start'      => 'Start packet capture on a specific interface',
      'sniffer_stop'       => 'Stop packet capture on a specific interface',
      'sniffer_stats'      => 'View statistics of an active capture',
      'sniffer_dump'       => 'Retrieve captured packet data to PCAP file',
      'sniffer_release'    => 'Free captured packets on a specific interface instead of downloading them',
    }

    #reqs = {
    #  'sniffer_interfaces' => [COMMAND_ID_SNIFFER_INTERFACES],
    #  'sniffer_start'      => [COMMAND_ID_SNIFFER_CAPTURE_START],
    #  'sniffer_stop'       => [COMMAND_ID_SNIFFER_CAPTURE_STOP],
    #  'sniffer_stats'      => [COMMAND_ID_SNIFFER_CAPTURE_STATS],
    #  'sniffer_dump'       => [COMMAND_ID_SNIFFER_CAPTURE_DUMP],
    #  'sniffer_release'    => [COMMAND_ID_SNIFFER_CAPTURE_RELEASE],
    #}

    #filter_commands(all, reqs)
  end


  def cmd_sniffer_interfaces(*args)

    ifaces = client.sniffer.interfaces()

    print_line()

    ifaces.each do |i|
      if i.length == 8
        # Windows
        print_line(sprintf("%d - '%s' ( type:%d mtu:%d usable:%s dhcp:%s wifi:%s )",
          i['idx'], i['description'],
          i['type'], i['mtu'], i['usable'], i['dhcp'], i['wireless'])
        )
      else
        # Mettle
        print_line(sprintf("%d - '%s' ( usable:%s )",
          i['idx'], i['description'], i['usable'])
        )
      end
    end

    print_line()

    return true
  end

  def cmd_sniffer_start(*args)
    intf = args.shift.to_i
    if (intf == 0)
      print_error("Usage: sniffer_start [interface-id] [packet-buffer (1-200000)] [bpf filter (posix meterpreter only)]")
      return
    end
    maxp = (args.shift || 50000).to_i
    bpf  = args.join(" ")

    client.sniffer.capture_start(intf, maxp, bpf)
    print_status("Capture started on interface #{intf} (#{maxp} packet buffer)")
    return true
  end

  def cmd_sniffer_stop(*args)
    intf = args[0].to_i
    if (intf == 0)
      print_error("Usage: sniffer_stop [interface-id]")
      return
    end

    res = client.sniffer.capture_stop(intf)
    print_status("Capture stopped on interface #{intf}")
    print_status("There are #{res[:packets]} packets (#{res[:bytes]} bytes) remaining")
    print_status("Download or release them using 'sniffer_dump' or 'sniffer_release'")
    return true
  end

  def cmd_sniffer_stats(*args)
    intf = args[0].to_i
    if (intf == 0)
      print_error("Usage: sniffer_stats [interface-id]")
      return
    end

    stats = client.sniffer.capture_stats(intf)
    print_status("Capture statistics for interface #{intf}")
    stats.each_key do |k|
      print_line("\t#{k}: #{stats[k]}")
    end

    return true
  end

  def cmd_sniffer_release(*args)
    intf = args[0].to_i
    if (intf == 0)
      print_error("Usage: sniffer_release [interface-id]")
      return
    end

    res = client.sniffer.capture_release(intf)
    print_status("Flushed #{res[:packets]} packets (#{res[:bytes]} bytes) from interface #{intf}")

    return true
  end

  def cmd_sniffer_dump(*args)
    intf = args[0].to_i
    if (intf == 0 or not args[1])
      print_error("Usage: sniffer_dump [interface-id] [pcap-file]")
      return
    end

    path_cap = args[1]
    path_raw = args[1] + '.raw'

    fd = ::File.new(path_raw, 'wb+')

    print_status("Flushing packet capture buffer for interface #{intf}...")
    res = client.sniffer.capture_dump(intf)
    print_status("Flushed #{res[:packets]} packets (#{res[:bytes]} bytes)")

    bytes_all = res[:bytes] || 0
    bytes_got = 0
    bytes_pct = 0
    linktype = res[:linktype]
    while (bytes_all > 0)
      res = client.sniffer.capture_dump_read(intf,1024*512)

      bytes_got += res[:bytes]

      pct = ((bytes_got.to_f / bytes_all.to_f) * 100).to_i
      if(pct > bytes_pct)
        print_status("Downloaded #{"%.3d" % pct}% (#{bytes_got}/#{bytes_all})...")
        bytes_pct = pct
      end
      break if res[:bytes] == 0
      fd.write(res[:data])
    end

    fd.close

    print_status("Download completed, converting to PCAP...")

    fd = nil
    if(::File.exist?(path_cap))
      fd = ::File.new(path_cap, 'ab+')
    else
      fd = ::File.new(path_cap, 'wb+')
      fd.write([0xa1b2c3d4, 2, 4, 0, 0, 65536, linktype].pack('NnnNNNN'))
    end

    pkts = {}
    od = ::File.new(path_raw, 'rb')


    # TODO: reorder packets based on the ID (only an issue if the buffer wraps)
    while(true)
      buf = od.read(20)
      break unless buf

      idh,idl,thi,tlo,len = buf.unpack('N5')
      break unless len
      if(len > 10000)
        print_error("Corrupted packet data (length:#{len})")
        break
      end

      pkt_id = (idh << 32) +idl
      pkt_ts = Rex::Proto::SMB::Utils.time_smb_to_unix(thi,tlo)
      pkt    = od.read(len)

      fd.write([pkt_ts,0,len,len].pack('NNNN')+pkt)
    end
    od.close
    fd.close

    ::File.unlink(path_raw)
    print_status("PCAP file written to #{path_cap}")
  end

  #
  # Name for this dispatcher
  # sni
  def name
    "Sniffer"
  end

end

end
end
end
end
