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

"""Module defining service related classes.

This module defines classes encapsulating service descriptions. In Zorp a
service defines how an established client request should be handled. When a
connection is accepted (usually by a listener, see the 'Listener' module),
the service bound to the given Listener is requested the create an instance
of itself (using the 'startInstance()' method, see below), which will handle
the connection and will take care about proxying traffic between the client
and the server.

Services are uniquely identified by their name, which is then used
as a reference to bind services to listeners. This name resolution
is done through the mapping named 'services'. As a new service
instance is created, it's name is registered in this hash.

The abstract interface a Service object is required to support is
defined in AbstractService, customized service classes can be derived
from this base class.
"""

from Stream import Stream
from Session import StackedSession
from Zorp import ServiceException, LimitException, log, CORE_SESSION
from Chainer import ConnectChainer
from Router import TransparentRouter

services = {}

class AbstractService:
	"""An abstract class defining the Service interface.
	
	This is an abstract class defining the interface Zorp uses, and as
	such it doesn't really do anything other than raising
	NotImplementedError exceptions in its methods. You should
	either derive a descendant from AbstractService, or use
	'Service' instead.

	Attributes
	
	  name		-- The name of the service
	  
	  instance_id	-- the session serviced by the service

	"""
	
	def __init__(self, name):
		"""Constructor to initialize an AbstractService or a derived class instance.

		This constructor sets attributes based on arguments, and
		registers this Service to the 'services' hash so that
		Listeners may resolve service names to service instances.
		
		Arguments

		  self  -- this instance
		  
		  name	-- The name of the service

		"""
		if services.has_key(name):
			raise ServiceException, "Duplicate service"
		services[name] = self
		self.name = name
		self.instance_id = 0
	
	def startInstance(self, session):
		"""Function to start an instance of this service.
		
		Abstract method to be implemented in derived classes.
		Should start an instance of the given service. A service
		instance takes care of the client connection, connects
		to the server and supervises the traffic going in either
		direction.
		
		Tasks of a service instance are implemented by classes
		derived from 'Proxy'.
		
		This method unconditionally raises a NotImplementedError
		exception to indicate that it must be overridden by
		descendant classes like 'Service'.
		   
		Arguments

		  self          --      this instance
		  
		  session	--	start service within this session

		"""
		raise NotImplementedError

	def startCTInstance(self):
		raise NotImplementedError

	def addClient(self, fd):
		raise NotImplementedError

	def stopInstance(self, session):
		"""Function called when an instance of this service is ended
		
		This function is called by Session.__del__ and indicates
		that a given session (instance) of this service is ended.
		
		Arguments
		
		  self -- this instance
		  
		  session -- ending session
		"""
		raise NotImplementedError
		
	def __str__(self):
		"""Function to represent this object as a string
		
		This function is called by the Python core when this object
		is used as-, or casted to a string. It simply returns
		the service name.
		
		Arguments
		
		  self -- this instance
		"""
		return self.name

class Service(AbstractService):
	"""Class implementing the interface defined in AbstractService.
	
	This service class uses the standard Zorp provided Proxy class
	to implement the tasks required by a Service instance.
		
	Attributes
	
	  router       -- router to use

	  chainer      -- The chainer class used for the service
	  
	  snat         -- snat to use
	  
	  dnat         -- dnat to use
	  
	  proxy_class  -- The proxy class used for the service
	  
	  auth	       -- Authentication handling class 
	                  (None if no authentication)

          auth_name    -- Authentication name of this service. This
                          string is sent to authentication agents like
                          Satyr to inform the client which service
                          he is authenticating for.

	"""

	def __init__(self, name, proxy_class, router=None, chainer=None, snat=None, dnat=None, auth=None, max_instances=0, auth_name=None):
		"""Initializes a Service instance.
		
		This function takes its arguments, and initializes
		appropriate attributes in self.
		   
		Arguments
		
		  self       -- this instance
		
		  name	     -- Name of this service (used in access control)
		  
		  router     -- The router to use to determine server address, defaults to TransparentRouter if empty
		  
		  chainer    -- chainer to use, defaults to ConnectChainer if empty
		  
		  snat       -- snat to use
		  
		  dnat       -- dnat to use
		  
		  proxy_class--	The proxy class used for the service
		  
		  auth	     -- Authentication handling class (None if
		                no authentication)
                  
                  max_instances -- maximum number of simultaneous instances of this service
                  
                  auth_name  -- authentication name sent to authentication agents, 
                                service name is used if empty


		"""
		AbstractService.__init__(self, name)
		self.proxy_class = proxy_class
		self.router = router
		self.chainer = chainer
		self.snat = snat
		self.dnat = dnat
		self.auth = auth
		self.auth_name = auth_name or name
		
		if not self.router:
			self.router = TransparentRouter()
		if not self.chainer:
			self.chainer = ConnectChainer()
		
		self.max_instances = max_instances
		self.num_instances = 0

	def startInstance(self, session, type):
		"""Start a service instance.
		
		Called by the Listener to create an instance of this
		service.
		   
		Arguments

		  self          --      this instance
		  
		  session	--	The session object
			
		"""
		if self.max_instances != 0 and self.num_instances >= self.max_instances:
			raise LimitException
		
		if session.auth == None:
			session.auth = self.auth		

		self.num_instances = self.num_instances + 1
		session.started = 1

		session.name = self.name
		instance_id = self.instance_id
		
		# FIXME: handle wraparound
		self.instance_id = self.instance_id + 1
		
		session.setServiceInstance(instance_id)
		log(CORE_SESSION, 3, "%s: Starting proxy instance; client_fd='%d', client_address='%s', client_zone='%s', client_local='%s'" % (session.session_id, session.client_fd, session.client_address, session.client_zone, session.client_local))
		ss = StackedSession(session, self.chainer)
		ss.client_stream = Stream(ss.client_fd, ss.session_id + ": client")
		return self.proxy_class(ss, type)


	def stopInstance(self, session):
		"""Function called when a session terminates.

		This function is called when a session terminates. It
		decrements concurrent session count.

		Arguments

		  self -- this instance

		  session -- session we belong to
		"""
		if session.started:
			self.num_instances = self.num_instances - 1

	def startCTInstance(self, fd):
		"""Function starting the connection tracker.

		This function is called once for each service to
		start the corresponding connection tracker. It fetches
		a reference to the connection tracker class from the
		proxy class 'conntrack' field, and instantiates it as
		a class.
		
		Arguments

		  self -- this instance

		  fd -- first fd to add

		"""
		self.ctrack = self.proxy_class.conntrack(self)
		self.ctrack.addClient(fd)
		self.ctrack.startInstance()

	def addClient(self, fd):
		"""Function to add a new fd to the set of watched fds.

		This function is called to add a new fd to the set of
		watched fds.

		Arguments

		  self -- this instance

		  fd -- new fd to add
		"""
		self.ctrack.addClient(fd)

