"""Module defining interface to the Telnet proxy.

This module defines the interface to the Telnet proxy implemented by
the Zorp Telnet module, and provides pre-configured classes for different
tasks.
"""


from Zorp import *
from Proxy import Proxy

TELNET_OPT_ACCEPT = 1
TELNET_OPT_REJECT = 3
TELNET_OPT_ABORT  = 4
TELNET_OPT_DROP   = 5
TELNET_OPT_POLICY = 6

TELNET_TERMINAL_TYPE      = "24"
TELNET_TERMINAL_SPEED     = "32"
TELNET_X_DISPLAY_LOCATION = "35"
TELNET_ENVIRONMENT        = "39"
TELNET_SUPPRESS_GO_AHEAD  = "3"
TELNET_ECHO               = "1"
TELNET_NAWS               = "31"

TELNET_SB_IS   = "0"
TELNET_SB_SEND = "1"
TELNET_SB_INFO = "2"

TELNET_POLICY = "telnet.policy"

class AbstractTelnetProxy(Proxy):
	"""Wrapper class for the Telnet proxy

	TelnetProxy is a wrapper class for the built-in Telnet proxy in Zorp. It
	implements the Telnet protocol, as described in RFC 854, and most common
	extensions. Although not all possible options are checked at the low
	level proxy, the administrator has the possibility to filter any option
	and suboption negotiation sequence using policy callbacks.

	Usage

	  Default policy

	    The low level Telnet proxy denies every option and suboption negotiation
	    sequences by default, and provides the administrator with methods to
	    change this default behaviour. As the low level implementation denies
	    everything, the default policy of the TelnetProxy class has been set
	    so that all important options (which are necessary to use Telnet to
	    log in to other machines) and their suboption negotiation sequences are
	    allowed. These are: Telnet Terminal Type, Telnet X Display Location,
	    Telnet Environment, Telnet Suppress Go Ahead, Telnet NAWS (Negotiation
	    About Window Size), and Telnet Echo.

	  Setting policy in general

	    As Telnet is a symmetrical protocol, there is no point in dividing the
	    data stream into requests and responses. Instead of this, the administrator
	    can define which options the Telnet session may use, and can filter the
	    suboption negotiation sequences of these options. Changing the default policy
	    can be done using the multi-dimensional hash (DimHash) 'policy'. It is
	    indexed with one or two values: the first is the option, and the optional
	    second one is the suboption which you want to filter.

	  Setting policy for option negotiation

	    To determine which options to enable in the session, the low level Telnet
	    proxy consults the 'policy' hash: the proxy looks up the hash value corresponding
	    the option number as a key. If the hash contains no entry for that number, the
	    default policy '*' is looked used. If there is no default policy defined,
	    the option negotiation is denied: the option won't be used in the session.

	    If a match is found, the corresponding tuple is used to determine the action
	    to take. This tuple consists of one or two elements. The first is an integer,
	    the second is optional: if the first value is TELNET_OPTION_POLICY, the second
	    must be the name of the callback function)

	      TELNET_OPT_ACCEPT -- allow option

	      TELNET_OPT_DROP   -- drop option

	      TELNET_OPT_ABORT  -- deny option and terminate the Telnet session

	      TELNET_OPT_POLICY -- call the function given in the second tuple item
	      			      This second item must be a callable Python function,
				      taking exactly two parameters: self and option (option
				      is an integer). The function must return one of these
				      action codes, excluding TELNET_OPTION_POLICY.

	      Example (Sample for disabling Telnet X Display Location option)

	        class MyTelnetProxy(TelnetProxy):

		  def config(self):
		    TelnetProxy.config(self)
		    self.policy[TELNET_X_DISPLAY_LOCATION] = (TELNET_OPT_DROP)

		    
	  Setting policy for suboption negotiation

	    To change the default processing (deny) of suboption negotiation sequences,
	    you must enable the suboption negotiation subcommands for each option. You
	    can do this by adding tuples to the 'policy' hash, indexed by two values: the
	    option and the suboption negotiation subcommand. For example, if the low
	    level implementation detects that a suboption negotiation sequence follows,
	    it looks up the tuple corresponding the indexes (option number, subcommand).
	    If a match is not found, the subcommand is substituted with '*', and the lookup
	    is repeated. If there is still no match, the subcommand is restored and the
	    option is substituted with '*'. The last combination of index values looked up
	    is ('*', '*'). For example, for the SEND subcommand of Telnet Terminal Type
	    option, the following lookups are made (in this order):

	      1. "24", "1"	(24 is the code of Telnet Terminal Type option)
	      2. "24", "*"
	      3. "*",  "1"
	      4. "*",  "*"

	    If a match is found, the corresponding tuple is used to determine the necessary
	    action. This tuple has one or two elements: the first one defines the action,
	    the second may be the name of a function, used only if the first one is
	    TELNET_OPTION_POLICY. Possible values for the first value:

	      TELNET_OPT_ACCEPT -- allow suboption negotiation sequence

	      TELNET_OPT_DROP   -- deny suboption negotiation sequence

	      TELNET_OPT_ABORT  -- deny suboption negotiation, and terminate session

	      TELNET_OPT_POLICY -- call the function given in the second tuple item
	      			      This second item must be a callable Python function,
				      taking exactly four parameters: self, option, name
				      and value. The function must return one of these
				      action codes, excluding TELNET_OPTION_POLICY.

	    Policy callback functions can be used to make decisions based on the
	    conent of the suboption negotiation sequence. For example, the Telnet
	    Environment option's suboption negotiation sequences transfer environment
	    variables. The low level proxy implementation parses these variables, and
	    passes their name and value to the callback function, one at a time. These
	    values van be also changed during transfer, by changing the var_name and
	    var_value attributes of the proxy class.

	    Example (Rewriting the DISPLAY environment variable):

	      class MyRewritingTelnetProxy(TelnetProxy):

	        def config(self):
		  TelnetProxy.config()
		  self.policy[TELNET_ENVIRONMENT, TELNET_SB_IS] = (TELNET_OPTION_POLICY, self.rewriteVar)

		def rewriteVar(self, option, name, value):
		  if name == "DISPLAY"
		  	self.var_value = "rewritten_value:0"
		  return TELNET_OPTION_ACCEPT

	Attributes
	
	  option    -- (dimhash) multi-dimensional policy hash, directing the
	  	       proxy to do something with incoming negotiation
		       sequences

	  current_var_name  -- (string) name of the variable negotiated

	  current_var_value -- (string) value of the variable negotiated (for example:
	  	       value of an environment variable, an X display location
		       value, etc.)

	  timeout   -- (integer) I/O timeout in milliseconds
	
	"""

	def __init__(self, session, type):
		"""Constructor to initialize a TelnetProxy instance.

		This function initializes a TelnetProxy instance by calling
		the inherited __init__ constructor with appropriate
		parameters.
		"""
		Proxy.__init__(self, "telnet", session, type)

	def config(self):
		pass

class TelnetProxy(AbstractTelnetProxy):

	def config(self):
		"""Configuration for TelnetProxy

		It enables all options needed for a useful Telnet session.
		"""
		self.option[TELNET_TERMINAL_TYPE]      = TELNET_OPT_ACCEPT
		self.option[TELNET_TERMINAL_SPEED]     = TELNET_OPT_ACCEPT
		self.option[TELNET_X_DISPLAY_LOCATION] = TELNET_OPT_ACCEPT
		self.option[TELNET_ENVIRONMENT]        = TELNET_OPT_ACCEPT
		self.option[TELNET_NAWS]               = TELNET_OPT_ACCEPT
		self.option[TELNET_SUPPRESS_GO_AHEAD]  = TELNET_OPT_ACCEPT
		self.option[TELNET_ECHO]               = TELNET_OPT_ACCEPT

		self.option["*"]                       = TELNET_OPT_REJECT

		self.option[TELNET_TERMINAL_TYPE,      "*"] = TELNET_OPT_ACCEPT
		self.option[TELNET_TERMINAL_SPEED,     "*"] = TELNET_OPT_ACCEPT
		self.option[TELNET_X_DISPLAY_LOCATION, "*"] = TELNET_OPT_ACCEPT
		self.option[TELNET_ENVIRONMENT,        "*"] = TELNET_OPT_ACCEPT
		self.option[TELNET_NAWS,               "*"] = TELNET_OPT_ACCEPT

		self.option["*"                        "*"] = TELNET_OPT_REJECT
