############################################################################
##
## Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
## All rights reserved.
##
## $Id: Listener.py,v 1.34.2.4.2.3 2001/09/10 10:59:19 bazsi Exp $
##
## Author  : Bazsi
## Auditor : kisza
## Last version: 1.15
## Notes:
##   - comments in English
##
############################################################################

"""Module implementing Listeners.

This module defines the Listener class, the main entry point for client
requests. Listener binds to a given address, waits for connections, and for
each connection spawns a new session to handle it.
"""

from Zorp import *
from Session import MasterSession
from Service import services
from traceback import print_exc

listen_hook = None
unlisten_hook = None

class SimpleListen:
	"""Class encapsulating a simple Listener.
	
	This class is a wrapper around the ZorpListen component and
	is used as a base for 'Listener'. In addition to performing
	Listening it calls 'listen_hook' and 'unlisten_hook' for
	allocated sockets.
	
	Attributes
	
	  listen -- a low level ZorpListen component instance
	  
	  local -- address we are bound to
	"""
	def __init__(self, bindto, accept_one=0, backlog=255):
		"""Constructor to initialize a SimpleListen instance
		
		This constructor initializes a SimpleListen instance
		by creating a lowlevel ZorpListen component, and
		setting appropriate arguments.
		
		Arguments
		
		  self -- this instance
		  
		  bindto -- address to bind to
		  
		  accept_one -- accept a single connection only

		  backlog -- backlog value to pass to listen()
		  
		"""
		
		global listen_hook
		
		self.listen = Listen(bindto, self.accepted, accept_one, backlog)
		self.bindto = bindto
	        self.local = self.listen.local
	        if listen_hook:
		        listen_hook(self)

	def __del__(self):
		"""Destructor to deinitialize a Listener instance.
		
		The primary function of this destructor is to call the
		unlisten_hook function, to make it possible to remove
		pending packet filter rules.
		
		Attributes
		
		  self -- this instance
		"""
		global unlisten_hook
		
		if unlisten_hook:
			unlisten_hook(self)
	
	def start(self):
		"""Function to actually start listening.
		
		This function calls the lowlevel start method to 
		actually start listening.
		
		Arguments
		
		  self -- this instance
		"""
	        self.listen.start()
	
	def accepted(self):
		"""Function called when a connection is established.
		
		This function is called when a connection is established. 
		It does nothing here, it should be overridden by descendant
		classes.
		
		Arguments
		
		  self -- this instance
		"""
		return Z_REJECT

	def destroy(self):
		"""Stops the listener on the given port

		Calls the destroy method of the low-level object

		Arguments
		
		  self -- this instance
		  
		"""
		self.listen.destroy()


class Listener(SimpleListen):
	"""Class encapsulating a Listener, which starts services for established connections.
	
	This class is the starting point of Zorp services. It listens on the
	given port, and when a connection is accepted it starts a session
	and the given service.
                   
	Attributes
	
	  listen --       A Zorp.Listen instance
	  
	  service --      the service to be started
	  
	  bindto --       bind address
	  
	  local --        local address where the listener is bound
	"""

	def __init__(self, bindto, service, transparent=FALSE, backlog=255):
		"""Constructor to initialize a Listen instance
		
		Creates the instance, sets the initial attributes, and
		starts the listener

		Arguments

		  self -- this instance
		  
		  bindto --  the address to bind to
		  
		  service -- the service name to start
		  
		  transparent -- TRUE if this is a listener of a 
		                 transparent service, specifying this is
		                 not mandatory but performs additional checks

		  backlog -- backlog value to pass to listen()
		  
		"""
		try:
			self.service = services[service]
		except KeyError:
			raise ServiceException, "Service %s not found" % (service,)
		self.bindto = bindto
	        self.transparent = transparent
	        SimpleListen.__init__(self, bindto, backlog=backlog)
	        self.start()
	        
	def accepted(self, client, fd):
		"""Callback to inform the python layer about incoming connections.
		
		This callback is called by the core when a connection is 
		accepted. Its primary function is to check access control
		(whether the client is permitted to connect to this port),
		and to spawn a new session to handle the connection.
		
		Exceptions raised due to policy violations are handled here.

		Arguments

		  self   --  this instance
		  
		  client --  the address of the client
		  
		  fd     --  the fd of the connection to the client
		  
		Returns
		
		  TRUE if the connection is accepted
		"""
		session = None
		try:
			session = MasterSession()
			
			session.setService(self.service)
			session.setClient(fd, client)
			session.client_local = getSockName(fd)
			session.client_dest = getTransparentDestination(fd)
			service = self.getService(session)
			
			service.router.routeConnection(session)
			
			if self.transparent and session.client_local.port == self.local.port:
				log(CORE_ERROR, 1, "%s: Transparent listener connected directly, dropping connection; local='%s', client_local='%s'" % (session.session_id, self.local, session.client_local))
			elif session.isClientPermitted() == Z_ACCEPT:
				log(CORE_DEBUG, 8, "%s: Connection accepted; client_address='%s'" % (session.session_id, client))
				session.service.startInstance(session, 0)
				return Z_ACCEPT
			raise DACException
		except ZoneException, s:
			log(CORE_POLICY, 1, "%s: Zone not found; zone='%s'" % (session.session_id, s))
		except DACException:
			log(CORE_POLICY, 1, "%s: DAC policy violation;" % (session.session_id))
		except MACException:
			log(CORE_POLICY, 1, "%s: MAC policy violation;" % (session.session_id))
		except AuthException:
			log(CORE_POLICY, 1, "%s: Authentication failure;" % (session.session_id))
		except LimitException:
			log(CORE_POLICY, 1, "%s: Connection over permitted limits;" % (session.session_id))
		except:
			print_exc()
			
		if session != None: 
			session.destroy()

		return Z_REJECT

	def getService(self, session):
		"""Returns the service associated with the listener

		Returns the service to start.

		Arguments

		  self    -- this instance
		  
		  session -- session reference
		"""
		return self.service


class ZoneListener(Listener):
	"""Class to listen on the selected address, and start a service based on the client's zone.
	
	This class is similar to a simple Listener, but instead of
	starting a fixed service, it chooses one based on the client
	zone.
	
	It takes a mapping of services indexed by a zone name, with
	an exception of the '*' service, which matches anything.
	
	Attributes
	
	  services -- services mapping indexed by zone name
	"""
	
	def __init__(self, bindto, services):
		"""Constructor to initialize a ZoneListener instance.
		
		This constructor initializes a ZoneListener instance and sets
		its initial attributes based on arguments.
		
		Arguments
		 
		  self -- this instance
		  
		  bindto -- bind to this address
		  
		  services -- a mapping between zone names and services
		"""
		Listener.__init__(self, bindto, None)
		self.services = services
	
	def getService(self, session):
		"""Virtual function which returns the service to be ran
		
		This function is called by our base class to find out the
		service to be used for the current session. It uses the
		client zone name to decide which service to use.
		
		Arguments
		
		  self -- this instance
		  
		  session -- session we are starting
		  
		"""
		try:
			return self.services[session.client_zone];
		except KeyError:
			return self.services["*"]

