############################################################################
##
## Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
## All rights reserved.
##
## $Id: Zone.py,v 1.33.2.3.2.1 2001/05/11 14:19:10 bazsi Exp $
##
## Author  : Bazsi
## Auditor : kisza
## Last version: 1.11
## Notes:
##   - comments in English
##
############################################################################

"""Module defining Zone and related classes.

This module defines 'Zone' and related classes. Zones are the basis in
access control in Zorp.
"""

from Zorp import *
from Domain import InetDomain
from traceback import print_exc
import types

#labelset_class = LabelSet;
zones = {}
root_zone = None

class RootZone:
	"""Base zone class.
	
	A zone is the basis of access control in Zorp. It encapsulates an
	address range (for example an IPv4 subnet). Parsing and representing
	an address is done by the Domain class and derivates.
	
	Zones are organized into two different hierarchies. The first is
	based on the address it encapsulates. In this case a zone is a child
	of some other zone, if the parent is containing it. (For example
	0.0.0.0/0 contains 192.168.0.0/24) This hierarchy is used when
	searching the containing zone of a given network address.
	
	The other hierarchy is based on administrative decisions.
	Administrative children inherit security attributes (permitted
	set of services etc.) from their parents.
	
	The RootZone class serves two purposes:
	
	  1. it is the base class for all Zone-like classes, implementing
	     interfaces for access control, and hierarchy travelsal.
	  
	  2. an instance of this class is an address domain independent root
	     of the Zone address hierarchy and delegates searches to
	     the appropriate Zone.
	  
	Attributes
	
	  name -- a unique name of this zone
	  
	  inbound_services -- mapping indexed by service name, containing an
	                      item for each permitted inbound service
	  
	  outbound_services -- similar to inbound_services, but used for outbound
	                       services.

	  admin_parent      -- parent of this zone in the administrative hierarchy
	  
	  umbrella          -- true for umbrella zones (ie. zones that do
	                       not inherit security attributes from their
	                       administrative parents)
	
	"""
	def __init__(self, name, inbound_services=None, outbound_services=None, admin_parent=None, umbrella=0):
		"""Constructor to initialize a RootZone instance.
		
		This constructor is primarily called by derived classes to
		initialize basic Zone data structures.
		
		Arguments
		
		  self -- this instance
		  
		  name -- name of this zone
		  
		  inbound_services -- an array of allowed inbound service names
		  
		  outbound_services -- an array of allowed outbound service names
		  
		  admin_parent -- administrative parent name
		  
		  umbrella -- this is an umbrella zone
		  
		  domain  -- address domain
		""" 
		
		global zones
		
		if zones.has_key(name):
			raise ZoneException, 'Zone %s duplicate name' % name
		zones[name] = self
		self.name = name
		self.addr_parent = None
		self.addr_children = []
		self.umbrella = umbrella
		self.admin_children = []
		self.domain = None
		if admin_parent:
			self.admin_parent = zones[admin_parent]
			self.admin_parent.addAdminChild(self)
		else:
			self.admin_parent = None

                self.inbound_services = {}
                self.outbound_services = {}
                if outbound_services != None:
                        for i in outbound_services:
                                self.outbound_services[i] = 1
				debug(5, "Zone(%s): outbound service=%s" % (self.name, i))
        
                if inbound_services != None:            
                        for i in inbound_services:
                                self.inbound_services[i] = 1
				debug(5, "Zone(%s): inbound service=%s" % (self.name, i))
		
	def findDomain(self, domain):
		"""Function to find the root Zone of the given address domain.
		
		This function finds the first child in self which uses
		*domain* as address domain.
		
		Arguments
		
		  self -- this instance
		  
		  domain -- class implementing address range specifics (for
			    example InetDomain for IPv4)
		"""
		
		for x in self.addr_children:
			if x.domain is domain:
				return x
			
		# we never return self, because RootZone is not a real zone
		raise ZoneException, "Zone not found for domain %s" % (domain, )

	def findZone(self, address):
		"""Function to find the most specific Zone for address.
		
		This function searches the address hierarchy for the most
		specific Zone containing 'address'.
		
		Arguments
		
		  self -- this instance
		  
		  address -- address we are trying to find (should be
		             derived from SockAddr)
		
		"""
		for x in self.addr_children:
			try:
				if address < x.address:
					return x.findZone(address)
			except ValueError:
				pass
		raise ZoneException, "Address Domain not found for address %s" % (address, )
		
	def addAdminChild(self, child):
		"""Function to add an administrative child
		
		This function adds 'child' to the set of administrative children.
		
		Arguments
		
		  self -- this instance
		  
		  child -- child zone add
		  
		"""
		child.setAdminParent(self)
		self.admin_children.append(child)

	def setAdminParent(self, parent):
		"""Function to set administrative parent of this zone.
		
		This function sets the administrative parent of this Zone.
		
		Arguments
		
		  self -- this instance
		  
		  parent -- parent Zone
		"""
		self.admin_parent = parent
		
	def iterAdminChildren(self, fn, parm = None):
		"""Function to iterate over the set of administrative children.
		
		This function iterates over the set of administrative
		children calling the function 'fn' for each item, with
		parameters 'parm', 'self' and the item.

		The callback fn may delete items from admin_children, this
		function uses a local copy of that array.
		
		Arguments
		
		  self -- this instance
		  
		  fn -- function to call
		  
		  parm -- opaque object passed to fn
		
		""" 
		for x in self.admin_children[:]:
			fn(parm, self, x)

	def addAddrChild(self, child):
		"""Function to add a address-based child to this zone.
		
		This function adds a children to the set of child zones.
		
		Arguments
		
		  self -- this instance
		  
		  child -- child to add
		"""
		child.setAddrParent(self)
		self.addr_children.append(child)
		
	def setAddrParent(self, parent):
		"""Function to set address parent of this zone.
		
		This function sets the address parent of this Zone.
		
		Arguments
		
		  self -- this instance
		  
		  parent -- parent Zone
		"""
		
		self.addr_parent = self

	def delAddrChild(self, child):
		"""Function to delete an address child.
		
		This function removes an item from the set of address
		children.
		
		Arguments
		
		  self -- this instance
		  
		  child -- child to remove
		
		"""
		child.addr_parent = None
		self.addr_children.remove(child)

	def iterAddrChildren(self, fn, parm = None):
		"""Function to iterate over the set of address children
		
		This function iterates over the set of address children and
		calls 'fn' for each item.
		
		Arguments
		
		  self -- this instance
		  
		  fn -- function to call
		  
		  parm -- opaque argument passed to fn
		
		"""
		for x in self.addr_children[:]:
			fn(parm, self, x)

	def __str__(self):
		"""Overridden operator to return the textual representation of self.
		
		Called by the Python core to format the object contents when it is 
		written.
		
		Arguments
		
		  self -- this instance
		"""
		return "RootZone(%s)" % self.name

        def isInboundServicePermitted(self, session):
                """Function to do inbound access control check.
                
                This function is called when a session is connecting to a server to
                check whether it is permitted.
                
                Arguments
                
                  self -- this instance
                  
                  session -- session that should be checked
                  
                Returns
                
                  Z_ACCEPT if the service is permitted, Z_REJECT otherwise
                """
                try:
                        t = self.inbound_services["*"]
                except KeyError:
                        t = 0
                
                try:
                        t = self.inbound_services[session.service.name]
                except KeyError:
                        pass
                
                if t:
                        return Z_ACCEPT
                else:           
                	if self.admin_parent and not self.umbrella:
                		return self.admin_parent.isInboundServicePermitted(session)
                        return Z_REJECT
                
        def isOutboundServicePermitted(self, session):
                """Function to do outbound access control check.
                
                This function is called when an incoming connection is
		detected to check whether it is allowable.
                
                Arguments
                
                  self -- this instance
                  
                  session -- session that should be checked
                  
                Returns
                
                  Z_ACCEPT if the service is permitted, Z_REJECT otherwise
                """
                try:
                        t = self.outbound_services["*"]
                except KeyError:
                        t = 0
                
                try:
                        t = self.outbound_services[session.service.name]
                except KeyError:
                        pass
                
                if t:
                        return Z_ACCEPT
                else:           
                	if self.admin_parent and not self.umbrella:
                		return self.admin_parent.isOutboundServicePermitted(session)
                        return Z_REJECT
                                                
class Zone(RootZone):
	"""General Zone class using a real address range.
		
	This class differs from RootZone in that it uses a real address
	domain (for IPv4 InetDomain is used), unlike RootZone which
	is a general wrapper for all address types (IPv4, IPv6, SPX etc.)
	
	"""
	def __init__(self, name, addr, inbound_services = None, outbound_services = None, admin_parent = None, umbrella = 0, domain = None):
		"""Constructor to initialize a Zone instance.
		
		This class initializes a Zone instance by calling the
		inherited constructor, and setting local attributes. 
		
		Arguments
		
		  self -- this instance
		  
		  name -- name of this zone
		  
		  addr -- a string representing an address range
		          interpreted by the domain class (last argument),
		          *or* a list of strings representing multiple
		          address ranges.
		  
		  inbound_services  -- set of permitted inbound services as described by RootZone
		  
		  outbound_services -- set of permitted outbound services as described by RootZone
		  
		  admin_parent -- name of the administrative parent 
		  
		  umbrella  -- TRUE if this zone is an umbrella zone
		  
		  domain -- address domain class parsing 'addr' and performing 
		            address comparisons for IPv4 addresses it should 
		            be InetDomain
		
		Notes
		
		  If 'addr' is a list of addresses (like ['192.168.1.1',
		  '192.168.1.5']), several subzones are automatically
		  created with administrative parent set to self. This way
		  you can define members with additional privilege easily.
		
		"""
		
		RootZone.__init__(self, name, inbound_services, outbound_services, admin_parent, umbrella)
		
		if domain is None:
			raise ValueError, "domain must be defined"
		
		self.domain = domain
		if type(addr) == types.ListType:
			if len(addr) == 1:
				addr = addr[0]
			else:
				i = 0
				while i < len(addr):
	                        	Zone("%s-#%u" % (name, i), addr[i], admin_parent=self.name, domain=domain)
	                        	i = i + 1
				addr = None
		
		if addr:
			self.address = domain(addr)
			self.setAddrRelatives()
		else:
			self.address = None

	def setAddrRelatives(self):
		"""Helper function to place this zone into the address hierarchy.

		This function is called by the Zone constructor to
		place this zone into the address hierarchy.

		Arguments

		  self -- this instance
		
		"""
		def moveChildren(self, parent, zone):
			try:
				if zone.address < self.address:
					parent.delAddrChild(zone)
					self.addAddrChild(zone)
			except ValueError:
				pass


		try:
			root_domain = root_zone.findDomain(self.domain)
		except ZoneException:
			root_zone.addAddrChild(self)
			return

		try:
			zone = None
			zone = root_domain.findZone(self.address)
		except ZoneException:
			pass

		if zone == None:
			# current root domain is smaller than self
			root_zone.delAddrChild(root_domain)
			root_zone.addAddrChild(self)
			self.addAddrChild(root_domain)
			return
		if self.address == zone.address:
			raise NotImplementedError

				
		zone.iterAddrChildren(moveChildren, self)
		zone.addAddrChild(self)
				
	def findZone(self, addr):
		"""Function to find the most specific containing Zone of 'addr'

		This function returns the most specific Zone containing
		'addr'

		Arguments

		  self -- this instance

		  addr -- address to look up
		"""
		for zone in self.addr_children:
			try:
				return zone.findZone(addr)
			except ZoneException:
				pass
		try:
			if addr <= self.address:
				return self
		except ValueError:
			pass
		raise ZoneException, "Zone not found for address %s" % (addr, )

	def __str__(self):
		"""Format the Zone as string.

		This function is called by the Python core when this object
		is used as string.
		"""
		return "Zone(%s, %s)" % (self.name, self.address)
		

class InetZone(Zone):
	"""A class inherited from Zone using the InetDomain address type.

	This is a simple Zone class using InetDomain as its address
	type.
	
	"""
	def __init__(self, name, addr, inbound_services = None, outbound_services = None, admin_parent = None, umbrella = 0):
		"""Constructor to initialize an InetZone instance

		This constructor initializes an InetZone object instance,
		and sets its attributes based on arguments.

		Arguments

		  self -- this instance
		  
		  name -- name of this zone
		  
		  addr -- a string representing an address range,
		          interpreted by the domain class (last argument),
		          *or* a list of strings representing multiple
		          address ranges.
		  
		  inbound_services  -- set of permitted inbound services as described by RootZone
		  
		  outbound_services -- set of permitted outbound services as described by RootZone
		  
		  admin_parent -- name of the administrative parent 
		  
		  umbrella  -- TRUE if this zone is an umbrella zone
		  
		"""
		Zone.__init__(self, name, addr, inbound_services, outbound_services, admin_parent, umbrella, InetDomain)


root_zone = RootZone("root")

