############################################################################
##
## Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
## All rights reserved.
##
## $Id: Proxy.py,v 1.28.2.4.2.3 2001/09/19 19:22:17 bazsi Exp $
##
## Author  : Bazsi
## Auditor : kisza
## Last version: 1.13
## Notes:
##
############################################################################

"""Module defining classes encapsulating native proxies.

This module defines the class 'Proxy', encapsulating the ZorpProxy component
implemented by the Zorp core. Each protocol specific proxy is derived from
this class.
"""

from Zorp import *
from traceback import print_exc
from Stream import Stream
from SockAddr import SockAddrInet, getHostByName
from Session import StackedSession
import new

def proxyLog(self, type, level, msg):
	"""Function to log a proxy message.

	This function logs a message with the 'session_id' prepended.

	Arguments

	  self  -- should be a Proxy derivative

	  type  -- log class

	  level -- message verbosity level

	  msg   -- msg to log
	  
	"""
	#FIXME: performance check. (Check level before parsing string)
	try:
		mymsg = "%s: %s" % (self.session.session_id, msg)
	except:
		mymsg = "Invalid proxy instance: " % msg
	log(type, level, mymsg)

class Proxy(ZorpProxy):
	"""Base class for the proxies.

	This class is inherited from an ExtensionClass implemented by the
	Zorp core. It encapsulates a protocol specific proxy. As an instance
	is created, the required module is loaded (identified by the
	name attribute), and a new thread is started to handle the connection.
	
	Attributes
	
	  session      -- descriptor of the connection
	  
	  name         -- the native proxy module used for this session
	"""
	
	def __init__(self, name, session, type):
		"""Constructor to initialize a Proxy instance.

		This constructor initializes a Proxy instance, sets
		attributes and calls the underlying C extensionclass
		to actually create a proxy instance.
		
		Arguments
		
		  name	  -- The type of the proxy
		  
		  session -- reference to the session
		"""
		self.session = session
		self.name = name
		self.server_fd_picked = FALSE
		session.setProxy(name)
		log(CORE_SESSION, 4, "%s: Proxy starting; class='%s', module='%s'" % (session.session_id, self.__class__.__name__, name))
		try:
			ZorpProxy.__init__(self, name, session.session_id, session.client_stream, type)
		except:
			print_exc()
			session.destroy()

	def __del__(self):
		"""Destructor to deinitialize a Proxy instance.
		
		This destructor is called when this object instance is
		freed. It simply sends a message about this event to the
		log.
		
		Arguments
		
		  self -- this instance
		"""
		if not self.server_fd_picked:
			closeFd(self.session.server_fd)
		log(CORE_SESSION, 4, "%s: Proxy ending; class='%s', module='%s'" % (self.session.session_id, self.__class__.__name__, self.name))

	def __config__(self):
		if not self.session.auth_user and self.session.auth:
			self.session.auth.performAuth(self)

	def __destroy__(self):
		"""Function called by the proxy core when the session is to be freed.
		
		This function is called when the proxy module is to be freed. It
		simply sends a message about this event to the log.
		
		Arguments
		
		  self -- this instance
		"""
		try:
			if self.auth:
				self.auth.stopSession(self.session.session_id)
		except AttributeError:
			pass
		log(CORE_DEBUG, 6, "%s: Proxy destroy; class='%s', module='%s'" % (self.session.session_id, self.__class__.__name__, self.name))

	def addPolicy(self, klass):
		"""Adds a policy to the proxy.

		This function extends this class with another parent
		class at runtime. It can be used to add methods
		to this event handler class based on some
		runtime variable.
		
		Arguments

		  self        -- this instance
		  
		  klass	      -- the class implementing the policy

		"""
		self.__class__ = new.classobj(self.__class__.__name__, self.__class__.__bases__ + (klass,), self.__class__.__dict__)

        def stackProxy(self, client_fd, server_fd, proxy_class):
                """Function to actually stack a proxy within this proxy in a subsession.

                This function is called by the underlying C proxy to
                actually do stacking, it handles over the downstream
		filedescriptors and the class to create an instance of.
		The way the underlying proxy decides which proxy_class
		to use is proxy specific.

                Arguments

                  self -- this instance

                  client_fd -- client fd

                  server_fd -- server fd
                  
                  proxy_class -- proxy class to instantiate
                """

                proxyLog(self, CORE_DEBUG, 7, "Stacking child proxy; client_fd='%d', server_fd='%d', class='%s'" % (client_fd, server_fd, proxy_class.__name__))
                subsession = StackedSession(self.session)
                subsession.client_fd = client_fd
                subsession.client_stream = Stream(client_fd, "%s: client upstream" % self.session.session_id)
                subsession.server_fd = server_fd
                subsession.server_stream = None
                proxy_class(subsession, 0)

	def setServerAddress(self, host, port):
		# resolve host, port and store it in session.server_address
		# may raise an exception
		if self.session.server_address_inband:
			ip = getHostByName(host)
			self.session.server_address = SockAddrInet(ip, port)
			
	def connectServer(self):
		"""Function called to connect to the server endpoint or to the parent proxy.

		This function is called to establish connection with our
		server endpoint, which can be either a parent proxy we are
		stacked in, or a real connection to the server.

		If the chainer is defined, it is called with the given host
		and port parameters to connect to the remote server.

		If there is no chainer defined, it tries to return the
		server-side fd from the session. If there's no server_fd it
		raises an error.

		Arguments

		  self          -- this instance
		  
		Returns
		
		  The descriptor of the server stream

		"""
		self.server_fd_picked = TRUE
		if self.session.chainer == None:
			
			# we have no chainer, the server side fd
			# should be available by now, used in stacked
			# proxies
	                self.session.server_stream = Stream(self.session.server_fd, "%s: server upstream" % self.session.session_id)
			if self.session.server_fd == -1 or self.session.server_stream == None:
				raise InternalError, "No chainer and server_fd is -1 or server_stream is None"				
		else:
			self.session.chainer.chainParent(self.session)

		return self.session.server_stream
	
	def userAuthenticated(self, entity):
		"""Function called when inband authentication succeeds.

		This function is called by the low-level proxy module to
		indicate that inband authentication was successfully
		performed. The name of the client entity can be found in
		the 'entity' parameter.

		Arguments

		  self -- this instance

		  entity -- textual representation of the entity authenticated

		"""
		self.session.auth_user = entity
		proxyLog(self, CORE_AUTH, 3, "User authentication successful; entity='%s'" % entity)

class DatagramProxy(ZorpProxy):
	"""Base class for datagram based protocols.

	This class is similar to 'Proxy' with the exception that
	it's used for datagram based proxies, like Sink.

	Attributes

	  self          -- this instance
	  
	  session	-- the session we belong to
	"""
	
	def __init__(self, name, session):
		"""Initializes a DatagramProxy instance.

		Sets attributes based on arguments.

		Arguments

		  self          -- this instance
		  
		  name		-- The type of the proxy

		  session	-- The descriptor of the connection
		  
		"""
		self.session = session
		log(CORE_SESSION, 1, "%s: Proxy starting; class='%s', module='%s'" % (session.session_id, self.__class__.__name__, name));
		try:
			ZorpProxy.__init__(self, name, session.session_id, session.client_stream)
		except:
			session.destroy()
