############################################################################
##
## Copyright (c) 2000 BalaBit IT Ltd, Budapest, Hungary
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
##
## $Id: Ftp.py,v 1.10.2.4.2.3 2001/09/04 09:48:59 bazsi Exp $
##
## Author  : sasa
## Auditor :  
## Last version: 
## Notes:
##
############################################################################

"""Module exporting the Ftp proxy interface

This module defines the Ftp proxy interface as implemented by the
Ftp Zorp module.
"""

from Zorp import *
from Plug import PlugProxy
from Proxy import Proxy, proxyLog
from SockAddr import SockAddrInet, SockAddrInetRange
from Session import StackedSession
from Stream import Stream

FTP_DATA_KEEP    = 0
FTP_DATA_PASSIVE = 1
FTP_DATA_ACTIVE  = 2

FTP_SIDE_CLIENT = 0
FTP_SIDE_SERVER = 1


FTP_ACCEPT = 1
FTP_DENY   = 3
FTP_ABORT  = 4
FTP_POLICY = 6

FTP_REQ_ACCEPT = 1
FTP_REQ_REJECT = 3
FTP_REQ_ABORT  = 4
FTP_REQ_POLICY = 6

FTP_RSP_ACCEPT = 1
FTP_RSP_REJECT = 3
FTP_RSP_ABORT  = 4
FTP_RSP_POLICY = 6

FTP_DEBUG  = "ftp.debug"
FTP_ERROR  = "ftp.error"
FTP_POLICY = "ftp.policy"

#class FtpDataConnect:
#	"""Helper class for FtpProxy to build data connections.
#
#	FtpDataConnect is a helper class for the Ftp proxy, it builds
#	the data-connection.
#	
#	Attributes
#	
#	"""



class FtpDataProxy(PlugProxy):
	"""Class encapsulating an FTP data connection.
	
	This class is used as a top-level proxy for FTP data connections.
	It is a simple plug (e.g. no content verification is done), but
	this class ensures that only one way traffic is transmitted
	by setting the appropriate copy_to_* attributes in Plug.
	
	"""
	def config(self):
		"""Configuration for FtpDataProxy
		
		This function sets the configuration for FtpDataProxy.  Our
		desired configuration is to restrict data transfer to one
		way only according to 'self.session.way'.
		
		Attributes
		
		  self -- this instance
		"""
		if self.session.way == 'C':
			self.copy_to_server = 0
		elif self.session.way == 'S':
			self.copy_to_client = 0
		else:
			proxyLog(self, FTP_ERROR, 4, "Unknown direction in FtpDataProxy, bidirectional dataconnection is used.")

	def __destroy__(self):
		"""Function called when the session is terminated.
		
		We call the registered stop() function to indicate the end
		of our session.
		
		Arguments
		
		  self -- this instance
		"""
		self.session.stop()

class AbstractFtpProxy(Proxy):
	"""Class encapsulating the FTP proxy implemented in Zorp.
	
	This proxy implements the FTP protocol. It denies everything by
	default, so uou should dervice your customized Ftp proxy classes in
	order to do something useful, or use 'FtpProxy' or
	'FtpProxyRO', etc.

	Usage
	
	  All requests are denied in the low level proxy implementation by
	  default. To enable different commands and/or answers you'll need to
	  extend the low-level proxy in Python. To see how this is done,
	  see the next two sections.
	  
	  Setting policy for commands
	  
	    Changing the default behaviour of commands can be done using the
	    hash named 'self.command'. This hash is indexed by the command name
	    (e.g: USER or PWD), and each item in this hash is an action
	    tuple, defining proxy behaviour for the given command.
	    
	    The first item in this tuple is an integer value, determining
	    the action to take, and also the interpretation of the remaining
	    items in the tuple.
	  
	    Possible actions (Possible actions for commands in FTP)
	  
	      FTP_REQ_ACCEPT -- allow request without modification.

	      FTP_REQ_REJECT -- reject request. (default) Second parameter
	                        contains a string, which will be sent back to client.

	      FTP_REQ_POLICY -- call a Python function to decide what to do.
	                        this value uses an additional tuple item,
	                        which must be a callable Python function.
	                        The function takes two parameters:
	                        'self', 'command'

	      FTP_REQ_ABORT  -- reject request, and drop connection.
	  
	      Example (Sample for anonymous only sessions in FTP)

	        class AnonFtp(FtpProxy):

	          def config(self):
	            self.fw_server_data.ip_s="192.168.0.1"
                    self.fw_client_data.ip_s="10.0.0.1"
	            self.commands["USER"] = (Ftp.FTP_REQ_POLICY, self.pUser)
	            self.commands["*"] = (Ftp.FTP_REQ_ACCEPT)

	          def pUser(self,command):
	            if self.request_parameter == "ftp" or self.request_parameter == "anonymous":
	              return Ftp.FTP_REQ_ACCEPT
	            return Ftp.FTP_REQ_REJECT

	  Changing answer code or string

	    Like with commands, we have a hash for answers as well.  This
	    hash indexed by command and answer, and contains an 
	    action tuple in each item. A special wildcard character '*' matches
	    every command. Another feature is that you don't need to write the
	    full answer code, it's possible to write only the first, or the
	    first two digits. 
	    
	    For example, all three index below is valid (Valid response codes in FTP)

	      "PWD","200"  -- Match exactly the answer 200 coming in reply to a 
	                 PWD command.

	      "PWD","2"    -- Match every answer starting with '2' in reply to a 
	                 PWD command.

	      "*","20"     -- Match every answer between 200 and 209 in reply to 
	                 any command.

	    All answers are "rejected" by default. It's changed to 
	    "500 Error parsing answer", because dropping an answer is not
	    permitted by RFC959. You may enable all answers with "*","*". 
	    
	    The precedence in processing hashtable entries is the following:

	      1. Exact match. ("PWD","200")
	        
	      2. Exact command match, partial answer matches ("PWD","20", "PWD","2", "PWD","*")
	        
	      3. Wildcard command, with answer codes repeated as above. 
	         ("*","200", "*","20", "*","2", "*","*")

	    Possible values for the first item in the action tuple (Possible action codes for responses in FTP)

	      FTP_RSP_ACCEPT -- allow answer without modification.

	      FTP_RSP_REJECT -- change answer code and message. (default) 
	                        Second parameter contains a string, which 
	                        will be sent back to client.

	      FTP_RSP_POLICY -- call a Python function to decide what to do.
	                        this value uses an additional parameter,
	                        which must be a callable Python function.
	                        The function takes three parameters:
	                        self, command, answer

	      FTP_RSP_ABORT  -- deny request, and drop conection.
	
	Attributes 
	 
	  data_proxy              -- proxy to use for data connections
	  
	  data_port_min           -- ports should be allocated above this variable
	  
	  data_port_max           -- ports should be allocated below this variable
	  
	  data_mode               -- FTP_DATA_KEEP, FTP_DATA_PASSIVE or FTP_DATA_ACTIVE
	                             can be used to force a connection type in server
	                             side. (default: FTP_DATA_KEEP)

	  transparent_mode        -- (logical) TRUE for transparent proxy,
	                             FALSE otherwise (default: TRUE)
				     
	  fw_server_data          -- (ZorpSocket) Firewall address in server side.
	                             By default it contains the IP address of the
				     interface 'towards' the server.
				     
	  fw_client_data          -- (ZorpSocket) Firewall address in client side.
	                             By default it contains the IP address of the
				     interface 'towards' the client.
				     
	  permit_unknown_command  -- (boolean) Enable commands not understood
	                             (and not handled) by proxy. (Default: FALSE)
				     
	  permit_empty_command    -- (boolean) Enable lines without commands.
	                             (Default: FALSE)
				     
	  max_line_length         -- (integer) Maximum line lenght, what a proxy
	                             may transfer. (Default: 255)
				     
	  max_username_length     -- (integer) Maximum length of usernames.
	                             (Default: 32)
				     
	  max_password_length     -- (integer) Maximum length of passwords.
                                     (Default: 64)
				     
	  request                 -- (hash) normative policy hash, directing the
	                             proxy to do something with requests, without
	                             the need to call Python.  indexed by the
	                             command (e.g. "USER", "PWD" etc)
	                             (default: empty)
				     
	  response                -- (dimhash) normative policy hash directing the
	                             proxy to do something with answers. Indexed
	                             by the command, and the answer (e.g.
	                             "USER","331"; "PWD","200" etc) (default: empty)
				     
	  request_command         -- (string) When a command goes up to policy level,
	                             you may change it with this.
	                             FIXME: Not yet used.
				     
	  request_parameter       -- (string) When a command goes up to policy level,
	                             this variable contains command parameters.
				     
	  response_status         -- (string) When an answer goes up to policy level,
	                             this variable contains answer code.
				     
	  response_parameter      -- (string) When an answer go up to policy level,
	                             this variable contain answer parameters.
				     
	  restrict_client_connect -- (boolean) Restricting data connection in
	                             client side. Accept data connection only if
	                             it's coming from IP number equal with command
	                             channel endpoint. (Default: TRUE)
				     
	  restrict_server_connect -- (boolean) Restricting data connection in
	                             server side. Accept data connection only if
	                             it's coming from IP number equal with command
	                             channel endpoint. (Default: FALSE)
	  
	  target_port_range       -- [STRING:NULL:RW:R] In non-Transparent mode
	                             this set where client may connect trough
	                             ftp proxy

	Private attributes
	
	  The attributes discussed here are used by the internal FTP logic, and should not be changed. (Private attributes in FtpProxy)

	  state -- connection state 0 is initial, 1 one of the connections
	           were established, 2 both connections established ready to start data
	           proxy.
	           
	  modes -- array indexed by side, 'L' for Listen or 'C' for connect
	  
	  listens -- listen objects created by us, array indexed by side
	  
	  connects -- connect objects created by us, array indexed by side
	  
	  sides -- determines the order in which sides must be processed
	           (first listen/connect on sides[0] then on sides[1])
	  
	  fds -- array indexed by side, containing the side specific fd
	  
	  localsesa -- local socket adress in server side. Used when data
	               connections port is in a range.
	  
	  localclsa -- local socket adress in client side. Used when data
	               connecion port is in range.
	
	"""

	def __init__(self, session, type):
		"""Constructor to initialize an FtpProxy instance
		
		This constructor initializes an FtpProxy instance by
		calling the inherited __init__ constructor
		with appropriate parameters, and setting up
		local attributes based on arguments.
		
		Arguments

		  self          --      this instance
		  
		  session	--	The session object
		  
		"""
		self.restrict_client_connect = TRUE
		self.restrict_server_connect = FALSE
		self.data_proxy = FtpDataProxy
		self.data_port_min = 40000
		self.data_port_max = 41000
		self.state = 0
		self.modes = [' ', ' ']
		self.listens = [None, None]
		self.connects = [None, None]
		self.sides = [-1, -1]
		self.fds = [-1, -1]
		self.localsesa = None
		self.localclsa = None
		Proxy.__init__(self, "ftp", session, type)
	
	def __destroy__(self):
 		"""Function to deinitialize an FtpProxy class.
 		
 		This function is called by the Zorp core when the proxy is
 		to be freed. We call resetState() here to destroy bound
 		listeners and connectors.
 		
 		Arguments
 		
	 	  self -- this instance
 		"""
 		Proxy.__destroy__(self)
		self.resetData()

	def resetData(self):
 		"""Function reseting the data connection state.
 		
 		Sets the initial state of everything, and destroys pending
		listeners.
 		
 		Arguments
 		  
 		  self -- this instance
 		  
 		"""
		for i in range(0, 2):
			if self.listens[i] != None:
				self.listens[i].destroy()
			if self.connects[i] != None:
				self.connects[i].destroy()
		self.listens = [None, None]
		self.connects = [None, None]
		self.way = 'N'
		self.state = 0

	def stopDataConnection(self):
		"""Function called by the data proxy to indicate the end of the data connection.
		
		This callback is registered to the data proxy 
		('FtpDataProxy' by default), to be called when
		the data connection is terminated. It's task is
		to signal this condition to the Ftp proxy core.
		
		Arguments
		  
		  self -- this instance

		"""
		self.ftpDataWakeup('D')

	def prepareData(self, side, mode):
		"""Function to prepare the data channel on 'side' side using 'mode' mode.

		This function is called by the proxy core to prepare the
		given 'side' of the data connection according to 'mode'. 
		This means either to listen for connections (like passive
		mode on the client side) or to start establishing a
		connection (passive mode on the server side).

		Arguments
		
		  self  -- this instance
		  
		  side  -- FTP_SIDE_CLIENT or FTP_SIDE_SERVER representing
                           either the client or the server
                           
                  mode  -- 'L' or 'C' for Listen or Connect
                  
		"""
		if (side != FTP_SIDE_CLIENT and side != FTP_SIDE_SERVER):
			return 0

		if (side == FTP_SIDE_CLIENT):
			local = self.fw_client_data
			if local.ip == 0:
				local.ip = self.session.client_local.ip

			remote = self.client_data

			if local.port==0:
				if self.data_port_min == self.data_port_max == 0:
					self.localclsa = SockAddrInet(local.ip_s, 0)
				else:
					if self.localclsa == None:
						self.localclsa = SockAddrInetRange(local.ip_s, self.data_port_min, self.data_port_max)
				
				sa = self.localclsa
			else:
				sa = SockAddrInet(local.ip_s, local.port)
		
		elif (side == FTP_SIDE_SERVER):
			local = self.fw_server_data
			if local.ip == 0:
				local.ip = self.session.server_local.ip

			remote = self.server_data

			if self.data_port_min == self.data_port_max == 0:
				self.localsesa = SockAddrInet(local.ip_s, 0)
			else:
				if self.localsesa == None:
					self.localsesa = SockAddrInetRange(local.ip_s, self.data_port_min, self.data_port_max)
			sa = self.localsesa
			
		self.modes[side] = mode
		if (mode == 'L'):
			
			if self.listens[side] != None:
				self.listens[side].destroy()
				self.listens[side] = None
				proxyLog(self, FTP_ERROR, 4, "self.listen not None!")

			try:

				self.listens[side] = \
					     Listen(sa, 
						    self.acceptedCallback, 
					   	    1, 1)
				if side == FTP_SIDE_CLIENT:
					self.fw_client_data = self.listens[side].local
				elif side == FTP_SIDE_SERVER:
					self.fw_server_data = self.listens[side].local
			except IOError:
				self.listens[side] = None
			
		elif (mode == 'C'):
			if self.connects[side] != None:
				self.connects[side].destroy()
				self.connects[side] = None
				proxyLog(self, FTP_ERROR, 4, "Self.connect not None!")
				
			try:
				self.connects[side] = \
					     Connect(sa, 
						     remote,
						     self.connectedCallback)
				if side == FTP_SIDE_CLIENT:
					self.fw_client_data = sa
				elif side == FTP_SIDE_SERVER:
					self.fw_server_data = sa
			except IOError:
				self.connects[side] = None

		return 0

	def startData(self, side1, side2, way):
		"""Function called by the proxy core to start the data connection procedure.
		
		This function starts the data connection proxy as specified 
		by previous calls to prepareData. We start the state machine
		here whose final state is to start a proxy on the established
		data connection.
		
		Arguments
		
		  side1  -- connect/listen on this side first
		  
		  side2  -- connect/listen on this side after the first one succeeded
		  
		  way    -- direction
		"""
		if side1 < 0 or side2 < 0 or side1 + side2 != 1:
			raise InternalError, "Side must be 0 or 1"
		else:
			if way != 'C' and way != 'S':
				raise InternalError, "Way must be C or S"
			else:
				self.sides = [side1, side2]
				self.state = 0
				self.way = way
				self.stepData()
                
	def stepData(self):
		"""Function called by the protocol logic to continue data establishment processing.
		
		This function is called each time after processing the
		current state finishes. This means that if the data
		connection on the server side is established we are
		ready to step one further and start accepting connections
		on the client side. 
		
		In state 0 and 1 it either listens or connects, based on
		the type field of parms[state].

		In state 2, stacks in the proxy and starts it with a
		session stacked out from self.session.

		Arguments
		
		  self -- this instance

		"""
		proxyLog(self, FTP_DEBUG, 7, "data listener, state %d" % (self.state))
		if self.state == 2:
        		# start proxy
			self.state = self.state + 1
			if self.fds[0] == 0 or self.fds[1] == 0:
				raise InternalError, "Any of fds is wrong!"
				self.resetstate()
				return

			proxyLog(self, FTP_DEBUG, 7, "stacking data proxy")
			
			# set up child session
			ss = StackedSession(self.session)
			ss.client_fd = self.fds[0]
			ss.client_stream = Stream(ss.client_fd, ss.session_id + 'data client')
			ss.server_fd = self.fds[1]
			ss.server_stream = Stream(ss.server_fd, ss.session_id + 'data server')
			ss.way = self.way
			ss.stop = self.stopDataConnection
			self.state = self.state + 1
			
			# start data proxy here
			self.data_proxy(ss, 0)
			
			return

		elif self.state > 2 or self.state < 0:
			raise InternalError, "self.state greater than 2!"
			return

		side = self.sides[self.state]
		mode = self.modes[side]
		if mode == 'L':
			try:
				if self.listens[side] != None:
					self.listens[side].start()
			except IOError:
				self.listens[side] = None
		elif mode == 'C':
			try:
				if self.connects[side] != None:
					self.connects[side].start()
			except IOError:
				self.connects[side] = None
		else:
			proxyLog(self, FTP_ERROR, 2, "Internal error. Connection mode not L or C")

	def acceptedCallback(self, client, fd):
		"""Function called when a connection was accepted on either side.

		This callback is called if any of the sides was in 'L' mode,
		and a connection was accepted on the listening socket. After
		storing the parameters we continue processing by skipping to
		the next state.
		
		Arguments
		
		  self          -- this instance
		  
		  client	-- the client address which has connected
		  
		  fd		-- the file descriptor for the connection
		  
		"""

		proxyLog(self, FTP_DEBUG, 7, "data listener, acceptedCallback")
		
		if self.state > 1 or self.state < 0:
			raise InternalError, "Connection doesn't have more than two side!"
			return 0

		side = self.sides[self.state]
		self.state = self.state + 1
		self.fds[side] = fd
		if side == FTP_SIDE_CLIENT:

			if self.restrict_client_connect:
				# bounce check
				if client.ip != self.session.client_address.ip:
					closeFd(fd)
					self.ftpDataWakeup('A')
					proxyLog(self, FTP_POLICY, 1, "Wrong IP when connecting in Client side.")
					return 0
			self.ftpDataWakeup('C')

		elif side == FTP_SIDE_SERVER:

			if self.restrict_server_connect:
				# bounce check
				if client.ip != self.session.server_address.ip:
					closeFd(fd)
					self.ftpDataWakeup('B')
					proxyLog(self, FTP_POLICY, 1, "Wrong IP when connecting in server side")
					return 0
			self.ftpDataWakeup('S')
		else:
			proxyLog(self.ftp_state, FTP_ERROR, 4, "acceptedCallback called with bad state:%d" % (self.state-1))

 	def connectedCallback(self, fd):
		"""Function called when a connection was established on either side.
		
		This function is similar to 'acceptedCallback', but used if
		we were in 'C' (connect) mode. After storing the parameters
		we continue processing by skipping to the next state.
		
		Arguments
		
		  self  --      this instance
		  
		  fd	--	the file descriptor for the connection
		  
		"""
		proxyLog(self, FTP_DEBUG, 7, "data listener, connectedCallback")
		
		if self.state > 1 or self.state < 0:
			raise InternalError, "Connection doesn't have more than two side!"
			return

		side = self.sides[self.state]
		self.state = self.state + 1
		self.fds[side] = fd

		if side == FTP_SIDE_CLIENT:
			self.ftpDataWakeup('C')
		elif side == FTP_SIDE_SERVER:
			self.ftpDataWakeup('S')
		else:
			proxyLog(self, FTP_ERROR, 4,"connectedCallback called with bade state:%d" % (self.state-1))

	def loadAnswers(self):
		"""This function can be called by derived classes to initialize internal hashtables.

		This function fills in the self.answers hash so that commonly used
		request/answer combinations are enabled.

		Arguments

		  self -- this instance
		  
		"""
		self.response["*","421"]    = (FTP_RSP_ABORT, "421 Logoff")
		self.response["*","500"]    = (FTP_RSP_ACCEPT)

		self.response["Null","120"] = (FTP_RSP_ACCEPT)
		self.response["Null","220"] = (FTP_RSP_ACCEPT)

		self.response["ABOR","225"] = (FTP_RSP_ACCEPT)
		self.response["ABOR","226"] = (FTP_RSP_ACCEPT)
		self.response["ABOR","501"] = (FTP_RSP_ACCEPT)
		self.response["ABOR","502"] = (FTP_RSP_ACCEPT)

		self.response["ALLO","200"] = (FTP_RSP_ACCEPT)
		self.response["ALLO","202"] = (FTP_RSP_ACCEPT)
		self.response["ALLO","501"] = (FTP_RSP_ACCEPT)
		self.response["ALLO","504"] = (FTP_RSP_ACCEPT)
		self.response["ALLO","530"] = (FTP_RSP_ACCEPT)

		self.response["APPE","110"] = (FTP_RSP_ACCEPT)
		self.response["APPE","125"] = (FTP_RSP_ACCEPT)
		self.response["APPE","150"] = (FTP_RSP_ACCEPT)
		self.response["APPE","226"] = (FTP_RSP_ACCEPT)
		self.response["APPE","250"] = (FTP_RSP_ACCEPT)
		self.response["APPE","425"] = (FTP_RSP_ACCEPT)
		self.response["APPE","426"] = (FTP_RSP_ACCEPT)
		self.response["APPE","450"] = (FTP_RSP_ACCEPT)
		self.response["APPE","451"] = (FTP_RSP_ACCEPT)
		self.response["APPE","452"] = (FTP_RSP_ACCEPT)
		self.response["APPE","501"] = (FTP_RSP_ACCEPT)
		self.response["APPE","502"] = (FTP_RSP_ACCEPT)
		self.response["APPE","530"] = (FTP_RSP_ACCEPT)
		self.response["APPE","551"] = (FTP_RSP_ACCEPT)
		self.response["APPE","552"] = (FTP_RSP_ACCEPT)
		self.response["APPE","532"] = (FTP_RSP_ACCEPT)
		self.response["APPE","550"] = (FTP_RSP_ACCEPT)
		self.response["APPE","553"] = (FTP_RSP_ACCEPT)

		self.response["CDUP","200"] = (FTP_RSP_ACCEPT)
		self.response["CDUP","501"] = (FTP_RSP_ACCEPT)
		self.response["CDUP","502"] = (FTP_RSP_ACCEPT)
		self.response["CDUP","530"] = (FTP_RSP_ACCEPT)
		self.response["CDUP","550"] = (FTP_RSP_ACCEPT)

		self.response["CWD","250"]  = (FTP_RSP_ACCEPT)
		self.response["CWD","501"]  = (FTP_RSP_ACCEPT)
		self.response["CWD","502"]  = (FTP_RSP_ACCEPT)
		self.response["CWD","530"]  = (FTP_RSP_ACCEPT)
		self.response["CWD","550"]  = (FTP_RSP_ACCEPT)

		self.response["DELE","250"] = (FTP_RSP_ACCEPT)
		self.response["DELE","450"] = (FTP_RSP_ACCEPT)
		self.response["DELE","550"] = (FTP_RSP_ACCEPT)
		self.response["DELE","501"] = (FTP_RSP_ACCEPT)
		self.response["DELE","502"] = (FTP_RSP_ACCEPT)
		self.response["DELE","530"] = (FTP_RSP_ACCEPT)

		self.response["HELP","211"] = (FTP_RSP_ACCEPT)
		self.response["HELP","214"] = (FTP_RSP_ACCEPT)
		self.response["HELP","501"] = (FTP_RSP_ACCEPT)
		self.response["HELP","502"] = (FTP_RSP_ACCEPT)

		self.response["LIST","125"] = (FTP_RSP_ACCEPT)
		self.response["LIST","150"] = (FTP_RSP_ACCEPT)
		self.response["LIST","226"] = (FTP_RSP_ACCEPT)
		self.response["LIST","250"] = (FTP_RSP_ACCEPT)
		self.response["LIST","425"] = (FTP_RSP_ACCEPT)
		self.response["LIST","426"] = (FTP_RSP_ACCEPT)
		self.response["LIST","451"] = (FTP_RSP_ACCEPT)
		self.response["LIST","450"] = (FTP_RSP_ACCEPT)
		self.response["LIST","501"] = (FTP_RSP_ACCEPT)
		self.response["LIST","502"] = (FTP_RSP_ACCEPT)
		self.response["LIST","530"] = (FTP_RSP_ACCEPT)

		self.response["MDTM","213"] = (FTP_RSP_ACCEPT)
		self.response["MDTM","501"] = (FTP_RSP_ACCEPT) #Hmmm.
		self.response["MDTM","550"] = (FTP_RSP_ACCEPT)

		self.response["MKD","257"]  = (FTP_RSP_ACCEPT)
		self.response["MKD","501"]  = (FTP_RSP_ACCEPT)
		self.response["MKD","502"]  = (FTP_RSP_ACCEPT)
		self.response["MKD","530"]  = (FTP_RSP_ACCEPT)
		self.response["MKD","550"]  = (FTP_RSP_ACCEPT)

		self.response["MODE","200"] = (FTP_RSP_ACCEPT)
		self.response["MODE","501"] = (FTP_RSP_ACCEPT)
		self.response["MODE","504"] = (FTP_RSP_ACCEPT)
		self.response["MODE","530"] = (FTP_RSP_ACCEPT)

		self.response["NLST","125"] = (FTP_RSP_ACCEPT)
		self.response["NLST","150"] = (FTP_RSP_ACCEPT)
		self.response["NLST","226"] = (FTP_RSP_ACCEPT)
		self.response["NLST","250"] = (FTP_RSP_ACCEPT)
		self.response["NLST","425"] = (FTP_RSP_ACCEPT)
		self.response["NLST","426"] = (FTP_RSP_ACCEPT)
		self.response["NLST","450"] = (FTP_RSP_ACCEPT)
		self.response["NLST","451"] = (FTP_RSP_ACCEPT)
		self.response["NLST","501"] = (FTP_RSP_ACCEPT)
		self.response["NLST","502"] = (FTP_RSP_ACCEPT)
		self.response["NLST","530"] = (FTP_RSP_ACCEPT)

		self.response["NOOP","200"] = (FTP_RSP_ACCEPT)

		self.response["PASS","202"] = (FTP_RSP_ACCEPT)
		self.response["PASS","230"] = (FTP_RSP_ACCEPT)
		self.response["PASS","501"] = (FTP_RSP_ACCEPT)
		self.response["PASS","503"] = (FTP_RSP_ACCEPT)
		self.response["PASS","530"] = (FTP_RSP_ACCEPT)

		self.response["PASV","227"] = (FTP_RSP_ACCEPT)
		self.response["PASV","501"] = (FTP_RSP_ACCEPT)
		self.response["PASV","502"] = (FTP_RSP_ACCEPT)
		self.response["PASV","530"] = (FTP_RSP_ACCEPT)

		self.response["PORT","200"] = (FTP_RSP_ACCEPT)
		self.response["PORT","501"] = (FTP_RSP_ACCEPT)
		self.response["PORT","530"] = (FTP_RSP_ACCEPT)

		self.response["PWD","257"]  = (FTP_RSP_ACCEPT)
		self.response["PWD","501"]  = (FTP_RSP_ACCEPT)
		self.response["PWD","502"]  = (FTP_RSP_ACCEPT)
		self.response["PWD","550"]  = (FTP_RSP_ACCEPT)

		self.response["QUIT","221"] = (FTP_RSP_ACCEPT)

		self.response["REIN","120"] = (FTP_RSP_ACCEPT)
		self.response["REIN","220"] = (FTP_RSP_ACCEPT)
		self.response["REIN","502"] = (FTP_RSP_ACCEPT)

		self.response["REST","350"] = (FTP_RSP_ACCEPT)
		self.response["REST","501"] = (FTP_RSP_ACCEPT)
		self.response["REST","502"] = (FTP_RSP_ACCEPT)
		self.response["REST","530"] = (FTP_RSP_ACCEPT)

		self.response["RETR","110"] = (FTP_RSP_ACCEPT)
		self.response["RETR","125"] = (FTP_RSP_ACCEPT)
		self.response["RETR","150"] = (FTP_RSP_ACCEPT)
		self.response["RETR","226"] = (FTP_RSP_ACCEPT)
		self.response["RETR","250"] = (FTP_RSP_ACCEPT)
		self.response["RETR","425"] = (FTP_RSP_ACCEPT)
		self.response["RETR","426"] = (FTP_RSP_ACCEPT)
		self.response["RETR","450"] = (FTP_RSP_ACCEPT)
		self.response["RETR","451"] = (FTP_RSP_ACCEPT)
		self.response["RETR","452"] = (FTP_RSP_ACCEPT)
		self.response["RETR","501"] = (FTP_RSP_ACCEPT)
		self.response["RETR","530"] = (FTP_RSP_ACCEPT)
		self.response["RETR","532"] = (FTP_RSP_ACCEPT)
		self.response["RETR","550"] = (FTP_RSP_ACCEPT) #TRY
		self.response["RETR","553"] = (FTP_RSP_ACCEPT)

		self.response["RMD","250"]  = (FTP_RSP_ACCEPT)
		self.response["RMD","501"]  = (FTP_RSP_ACCEPT)
		self.response["RMD","502"]  = (FTP_RSP_ACCEPT)
		self.response["RMD","530"]  = (FTP_RSP_ACCEPT)
		self.response["RMD","550"]  = (FTP_RSP_ACCEPT)

		self.response["RNFR","350"] = (FTP_RSP_ACCEPT)
		self.response["RNFR","450"] = (FTP_RSP_ACCEPT)
		self.response["RNFR","501"] = (FTP_RSP_ACCEPT)
		self.response["RNFR","502"] = (FTP_RSP_ACCEPT)
		self.response["RNFR","530"] = (FTP_RSP_ACCEPT)
		self.response["RNFR","550"] = (FTP_RSP_ACCEPT)

		self.response["RNTO","250"] = (FTP_RSP_ACCEPT)
		self.response["RNTO","501"] = (FTP_RSP_ACCEPT)
		self.response["RNTO","502"] = (FTP_RSP_ACCEPT)
		self.response["RNTO","530"] = (FTP_RSP_ACCEPT)
		self.response["RNTO","532"] = (FTP_RSP_ACCEPT)
		self.response["RNTO","553"] = (FTP_RSP_ACCEPT)

		self.response["SITE","200"] = (FTP_RSP_ACCEPT)
		self.response["SITE","202"] = (FTP_RSP_ACCEPT)
		self.response["SITE","501"] = (FTP_RSP_ACCEPT)
		self.response["SITE","530"] = (FTP_RSP_ACCEPT)

		self.response["SIZE","213"] = (FTP_RSP_ACCEPT)
		self.response["SIZE","550"] = (FTP_RSP_ACCEPT)

		self.response["SMNT","202"] = (FTP_RSP_ACCEPT)
		self.response["SMNT","250"] = (FTP_RSP_ACCEPT)
		self.response["SMNT","501"] = (FTP_RSP_ACCEPT)
		self.response["SMNT","502"] = (FTP_RSP_ACCEPT)
		self.response["SMNT","530"] = (FTP_RSP_ACCEPT)
		self.response["SMNT","550"] = (FTP_RSP_ACCEPT)

		self.response["STAT","211"] = (FTP_RSP_ACCEPT)
		self.response["STAT","212"] = (FTP_RSP_ACCEPT)
		self.response["STAT","213"] = (FTP_RSP_ACCEPT)
		self.response["STAT","450"] = (FTP_RSP_ACCEPT)
		self.response["STAT","501"] = (FTP_RSP_ACCEPT)
		self.response["STAT","502"] = (FTP_RSP_ACCEPT)
		self.response["STAT","530"] = (FTP_RSP_ACCEPT)

		self.response["STOR","110"] = (FTP_RSP_ACCEPT)
		self.response["STOR","125"] = (FTP_RSP_ACCEPT)
		self.response["STOR","150"] = (FTP_RSP_ACCEPT)
		self.response["STOR","226"] = (FTP_RSP_ACCEPT)
		self.response["STOR","250"] = (FTP_RSP_ACCEPT)
		self.response["STOR","425"] = (FTP_RSP_ACCEPT)
		self.response["STOR","426"] = (FTP_RSP_ACCEPT)
		self.response["STOR","450"] = (FTP_RSP_ACCEPT)
		self.response["STOR","451"] = (FTP_RSP_ACCEPT)
		self.response["STOR","452"] = (FTP_RSP_ACCEPT)
		self.response["STOR","501"] = (FTP_RSP_ACCEPT)
		self.response["STOR","530"] = (FTP_RSP_ACCEPT)
		self.response["STOR","532"] = (FTP_RSP_ACCEPT)
		self.response["STOR","551"] = (FTP_RSP_ACCEPT)
		self.response["STOR","552"] = (FTP_RSP_ACCEPT)
		self.response["STOR","553"] = (FTP_RSP_ACCEPT)

		self.response["STOU","110"] = (FTP_RSP_ACCEPT)
		self.response["STOU","125"] = (FTP_RSP_ACCEPT)
		self.response["STOU","150"] = (FTP_RSP_ACCEPT)
		self.response["STOU","226"] = (FTP_RSP_ACCEPT)
		self.response["STOU","250"] = (FTP_RSP_ACCEPT)
		self.response["STOU","425"] = (FTP_RSP_ACCEPT)
		self.response["STOU","426"] = (FTP_RSP_ACCEPT)
		self.response["STOU","450"] = (FTP_RSP_ACCEPT)
		self.response["STOU","451"] = (FTP_RSP_ACCEPT)
		self.response["STOU","452"] = (FTP_RSP_ACCEPT)
		self.response["STOU","501"] = (FTP_RSP_ACCEPT)
		self.response["STOU","530"] = (FTP_RSP_ACCEPT)
		self.response["STOU","532"] = (FTP_RSP_ACCEPT)
		self.response["STOU","551"] = (FTP_RSP_ACCEPT)
		self.response["STOU","552"] = (FTP_RSP_ACCEPT)
		self.response["STOU","553"] = (FTP_RSP_ACCEPT)

		self.response["STRU","200"] = (FTP_RSP_ACCEPT)
		self.response["STRU","501"] = (FTP_RSP_ACCEPT)
		self.response["STRU","504"] = (FTP_RSP_ACCEPT)
		self.response["STRU","530"] = (FTP_RSP_ACCEPT)

		self.response["SYST","215"] = (FTP_RSP_ACCEPT)
		self.response["SYST","501"] = (FTP_RSP_ACCEPT)
		self.response["SYST","502"] = (FTP_RSP_ACCEPT)

		self.response["TYPE","200"] = (FTP_RSP_ACCEPT)
		self.response["TYPE","501"] = (FTP_RSP_ACCEPT)
		self.response["TYPE","504"] = (FTP_RSP_ACCEPT)
		self.response["TYPE","530"] = (FTP_RSP_ACCEPT)

		self.response["USER","230"] = (FTP_RSP_ACCEPT)
		self.response["USER","331"] = (FTP_RSP_ACCEPT)
		self.response["USER","501"] = (FTP_RSP_ACCEPT)
		self.response["USER","530"] = (FTP_RSP_ACCEPT)

	def loadMinimalCommands(self):
		"""This function enable some minimal command set

		This function enable a minimal set of commands, for various
		subclass
		"""
		self.request["ABOR"] = (FTP_REQ_ACCEPT)
		self.request["CDUP"] = (FTP_REQ_ACCEPT)
		self.request["CWD"]  = (FTP_REQ_ACCEPT)
		self.request["LIST"] = (FTP_REQ_ACCEPT)
		self.request["MODE"] = (FTP_REQ_ACCEPT)
		self.request["MDTM"] = (FTP_REQ_ACCEPT)
		self.request["NLST"] = (FTP_REQ_ACCEPT)
		self.request["NOOP"] = (FTP_REQ_ACCEPT)
		self.request["PASV"] = (FTP_REQ_ACCEPT)
		self.request["PASS"] = (FTP_REQ_ACCEPT)
		self.request["PORT"] = (FTP_REQ_ACCEPT)
		self.request["PWD"]  = (FTP_REQ_ACCEPT)
		self.request["QUIT"] = (FTP_REQ_ACCEPT)
		self.request["REST"] = (FTP_REQ_ACCEPT)
		self.request["RETR"] = (FTP_REQ_ACCEPT)
		self.request["SIZE"] = (FTP_REQ_ACCEPT)
		self.request["STAT"] = (FTP_REQ_ACCEPT)
		self.request["STRU"] = (FTP_REQ_ACCEPT)
		self.request["SYST"] = (FTP_REQ_ACCEPT)
		self.request["TYPE"] = (FTP_REQ_ACCEPT)

		self.request["FEAT"] = (FTP_REQ_REJECT)
		self.request["CLNT"] = (FTP_REQ_REJECT)
		self.request["XPWD"] = (FTP_REQ_REJECT)
		self.request["MACB"] = (FTP_REQ_REJECT)

class FtpProxy(AbstractFtpProxy):
	"""Class encapsulating a permitting ftp proxy which allows everything, even unknown commands.
	    
	This class encapsulates a permitting Ftp proxy allowing unknown commands
	and unknown answers.
	"""

	def config(self):
		"""Configuration for FtpProxy.
		
		Enables all commands by setting permit_unknown_commands to
		TRUE and adding two wildcard entries to the commands
		and answers hash.

		Arguments
		
		  self -- this instance
		
		"""
		self.request["*"] = (FTP_REQ_ACCEPT)
		self.response["*","421"]    = (FTP_RSP_ABORT, "421 Logoff")
		self.response["*", "*"] = (FTP_RSP_ACCEPT)
		self.permit_unknown_command = TRUE

class FtpProxyAnonRO(AbstractFtpProxy):
	"""Class encapsulating an Anonymous DO FTP proxy which allows minimal set of commands.

	This proxy enables only Anonymous login, with only downloading,
	and strictly checks commands and return codes. For unknown
	commands or replies the session is terminated.
	"""
	
	def pUser(self,command):
		if self.request_parameter == "ftp" or self.request_parameter == "anonymous":
			return FTP_REQ_ACCEPT
		return FTP_REQ_REJECT

	def __config__(self):
		"""Configuration for FtpProxyAnonRO

		It enables a minimal set of commands for a working Anonymous
		Download-Only FTP proxy, and sets permit_unknown_commands
		to FALSE.
		
		Arguments
		
		  self -- this instance
		
		"""

		AbstractFtpProxy.loadMinimalCommands(self)
		self.request["USER"] = (FTP_REQ_POLICY, pUser)

		self.request["*"]    = (FTP_REQ_REJECT)

		AbstractFtpProxy.loadAnswers(self)
		self.response["*","*"] = (FTP_RSP_REJECT)
		self.permit_unknown_command = FALSE

class FtpProxyRO(AbstractFtpProxy):
	"""Class encapsulating a Download-Only FTP proxy which allows minimal set of commands.

	This proxy enables the minimal required protocol elements, and strictly
	checks commands and return codes. For unknown commands or replies
	the session is terminated.
	"""
	
	def config(self):
		"""Configuration for FtpProxyRO

		It enables a minimal set of commands for a working
		Download-Only FTP proxy, and sets permit_unknown_commands
		to FALSE.
		
		Arguments
		
		  self -- this instance
		
		"""

		AbstractFtpProxy.loadMinimalCommands(self)

		self.request["USER"] = (FTP_REQ_ACCEPT)
		self.request["*"]    = (FTP_REQ_REJECT)

		FtpProxy.loadAnswers(self)
		self.response["*","*"] = (FTP_RSP_REJECT)
		self.permit_unknown_command = FALSE

class FtpProxyAnonRW(AbstractFtpProxy):
	"""Class encapsulating an Anonymous RW FTP proxy which allows minimal set of commands.

	This proxy enables only Anonymous login
	and strictly checks commands and return codes. For unknown
	commands or replies the session is terminated.
	"""
	
	def pUser(self,command):
		if self.request_parameter == "ftp" or self.request_parameter == "anonymous":
			return FTP_REQ_ACCEPT
		return FTP_REQ_REJECT

	def __config__(self):
		"""Configuration for FtpProxyAnonRO

		It enables a minimal set of commands for a working Anonymous
		FTP proxy, and sets permit_unknown_commands to FALSE.
		
		Arguments
		
		  self -- this instance
		
		"""
		AbstractFtpProxy.loadMinimalCommands(self)

		self.request["APPE"] = (FTP_REQ_ACCEPT)
		self.request["DELE"] = (FTP_REQ_ACCEPT)
		self.request["MKD"]  = (FTP_REQ_ACCEPT)
		self.request["RMD"]  = (FTP_REQ_ACCEPT)
		self.request["RNFR"] = (FTP_REQ_ACCEPT)
		self.request["RNTO"] = (FTP_REQ_ACCEPT)
		self.request["STOR"] = (FTP_REQ_ACCEPT)
		self.request["STOU"] = (FTP_REQ_ACCEPT)

		self.request["USER"] = (FTP_REQ_POLICY, self.pUser)

		self.request["*"]    = (FTP_REQ_REJECT)

		FtpProxy.loadAnswers(self)
		self.response["*","*"] = (FTP_RSP_REJECT)
		self.permit_unknown_command = FALSE

class FtpProxyRW(AbstractFtpProxy):
	"""Class encapsulating a FTP proxy which allows minimal set of commands.

	This proxy enables the minimal required protocol elements, and strictly
	checks commands and return codes. For unknown commands or replies
	the session is terminated.
	"""
	
	def config(self):
		"""Configuration for FtpProxyRW

		It enables a minimal set of commands for a working FTP
		proxy, and sets permit_unknown_commands to FALSE.
		
		Arguments
		
		  self -- this instance
		
		"""
		AbstractFtpProxy.loadMinimalCommands(self)

		self.request["APPE"] = (FTP_REQ_ACCEPT)
		self.request["DELE"] = (FTP_REQ_ACCEPT)
		self.request["MKD"]  = (FTP_REQ_ACCEPT)
		self.request["RMD"]  = (FTP_REQ_ACCEPT)
		self.request["RNFR"] = (FTP_REQ_ACCEPT)
		self.request["RNTO"] = (FTP_REQ_ACCEPT)
		self.request["STOR"] = (FTP_REQ_ACCEPT)
		self.request["STOU"] = (FTP_REQ_ACCEPT)
		self.request["USER"] = (FTP_REQ_ACCEPT)

		self.request["*"]    = (FTP_REQ_REJECT)

		AbstractFtpProxy.loadAnswers(self)
		self.response["*","*"] = (FTP_RSP_REJECT)
		self.permit_unknown_command = FALSE

class FtpProxyMinimal(FtpProxyRO):
	"""Alias FtpProxyRO
	"""
	pass