# -*- coding: binary -*-
module Msf

require 'msf/core/exploit/tcp'

###
#
# This module exposes methods that may be useful to exploits that deal with
# clients that speak the File Transfer Protocol (FTP).
#
###
module Exploit::Remote::FtpServer

	include Exploit::Remote::TcpServer

	#
	# Creates an instance of an FTP exploit module.
	#
	def initialize(info = {})
		super

		# Register the options that all FTP exploits may make use of.
		register_options(
			[
				OptPort.new('SRVPORT',    [ true, "The local port to listen on.", 21 ])
			], Msf::Exploit::Remote::FtpServer)
	end

	def setup
		super
		@state = {}
	end

	def on_client_connect(c)
		@state[c] = {
			:name => "#{c.peerhost}:#{c.peerport}",
			:ip   => c.peerhost,
			:port => c.peerport,
			:user => nil,
			:pass => nil,
			:cwd  => '/'
		}

		active_data_port_for_client(c, 20)

		c.put "220 FTP Server Ready\r\n"
	end

	def on_client_data(c)
		data = c.get_once
		return if not data

		cmd,arg = data.strip.split(/\s+/, 2)
		arg ||= ""

		return if not cmd

		# Allow per-command overrides
		if(self.respond_to?("on_client_command_#{cmd.downcase}"))
			return self.send("on_client_command_#{cmd.downcase}", c, arg)
		end

		case cmd.upcase
		when 'USER'
			@state[c][:user] = arg
			c.put "331 User name okay, need password...\r\n"
			return

		when 'PASS'
			@state[c][:pass] = arg
			print_status("#{@state[c][:name]} LOGIN #{@state[c][:user]} / #{@state[c][:pass]}")
			c.put "230 Login OK\r\n"
			return

		when 'QUIT'
			c.put "221 Logout\r\n"
			return

		when 'SYST'
			c.put "215 UNIX Type: L8\r\n"
			return

		when 'TYPE'
			c.put "200 Type is set\r\n"
			return

		when 'CWD'
			c.put "250 CWD command successful.\r\n"
			return

		when 'PWD'
			c.put "257 \"#{@state[c][:cwd]}\" is current directory.\r\n"
			return

		when 'SIZE'
			c.put "213 1\r\n"
			return

		when 'MDTM'
			c.put "213 #{Time.now.strftime("%Y%m%d%H%M%S")}\r\n"
			return

		when 'PORT'
			port = arg.split(',')[4,2]
			if(not port and port.length == 2)
				c.put("500 Illegal PORT command.\r\n")
				return
			end

			port = port.map{|x| x.to_i}.pack('C*').unpack('n')[0]
			active_data_port_for_client(c, port)

			c.put "200 PORT command successful.\r\n"
			return

		when 'PASV'
			daddr = Rex::Socket.source_address(c.peerhost)
			dport = passive_data_port_for_client(c)
			@state[c][:daddr] = daddr
			@state[c][:dport] = dport
			pasv  = (daddr.split('.') + [dport].pack('n').unpack('CC')).join(',')
			c.put "227 Entering Passive Mode (#{pasv})\r\n"
			return

		when /^(STOR|MKD|REM|DEL|RMD)$/
			c.put "500 Access denied\r\n"
			return

		else
			# Allow per-command overrides
			if(self.respond_to?("on_client_unknown_command"))
				return self.send("on_client_unknown_command", c, cmd.upcase, arg)
			end

			print_status("#{@state[c][:name]} UNKNOWN '#{cmd} #{arg}'")
			c.put("500 '#{cmd} #{arg}': command not understood.\r\n")
			return
		end

		return

	end

	def on_client_close(c)
		@state.delete(c)
	end

	def passive_data_port_for_client(c)
		@state[c][:mode] = :passive

		if(not @state[c][:passive_sock])
			s = Rex::Socket::TcpServer.create(
				'LocalHost' => '0.0.0.0',
				'LocalPort' => 0,
				'Context'   => { 'Msf' => framework, 'MsfExploit' => self }
			)

			dport = s.getsockname[2]

			@state[c][:passive_sock] = s
			@state[c][:passive_port] = dport
			add_socket(s)
		end

		@state[c][:passive_port]
	end

	def active_data_port_for_client(c,port)
		@state[c][:mode] = :active

		connector = Proc.new {
			host = c.peerhost.dup
			sock = Rex::Socket::Tcp.create(
				'PeerHost' => host,
				'PeerPort' => port,
				'Context'   => { 'Msf' => framework, 'MsfExploit' => self }
			)
			add_socket(sock)
			sock
		}

		@state[c][:active_connector] = connector
		@state[c][:active_port]      = port
	end


	def establish_data_connection(c)
		begin
		Timeout.timeout(20) do
			if(@state[c][:mode] == :active)
				return @state[c][:active_connector].call()
			end
			if(@state[c][:mode] == :passive)
				c = @state[c][:passive_sock].accept
				add_socket(c)
				return c
			end
		end

		rescue ::Exception => e
			print_error("Failed to establish data connection: #{e.class} #{e}")
		end

		nil
	end

end
end

