############################################################################
##
## Copyright (c) 2000-2015 BalaBit IT Ltd, Budapest, Hungary
## Copyright (c) 2015-2018 BalaSys IT Ltd, Budapest, Hungary
##
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##
############################################################################

"""
<module>
<summary>The Stack module defines the classes used to connect to a stacking provider.
</summary>
<description>
  <para>Zorp is capable of stacking, that is, handing over parts of the traffic to other modules for
  further inspection (e.g., to other proxies to inspect embedded protocols, to
  content vectoring modules for virus filtering, etc.). The Stack module defines the
  classes required for this functionality.
  </para>
  <para>
  Stacking in Zorp services is performed using <link linkend="python.Stack.StackingProvider">
  StackingProvider policies</link>, which reference the host that performs the stacked operations
    using the <link linkend="python.Stack.RemoteStackingBackend">
  RemoteStackingBackend</link> class.
  </para>
</description>
<metainfo/>
</module>
"""

from Zorp import *
import Globals

class StackingProvider(object):
    """
    <class type="stackingprov">
      <summary>This is a policy class that is used to reference a configured
      stacking provider in service definitions.
      </summary>
      <description>
        <para>Instances of the StackingProvider class are policies that define
        which remote stacking backend a particular service uses to inspect
        the contents of the traffic.
        </para>
        <example>
        <title>A simple StackingProvider class</title>
        <para>The following class creates a simple stacking provider that
        can be referenced in service definitions. The remote host that
        provides the stacking services is located under the
        <parameter>192.168.12.12</parameter> IP address.
        </para>
        <synopsis>StackingProvider(name="demo_stackingprovider", backend=RemoteStackingBackend(addrs=(SockAddrInet('192.168.12.12', 1318),)))</synopsis>
        </example>
        <example>
        <title>Using a StackingProvider in an FTP proxy</title>
        <para>The following classes define a stacking provider that
        can be accesses a local ZCV instance using a domain socket.
        This service provider is then used to filter FTP traffic.
        The configuration of the ZCV (i.e., what modules it uses to filter
        the traffic is not discussed here).
        </para>
        <synopsis>class StackingFtpProxy(FtpProxy):
def config(self):
    super(StackingFtpProxy, self).config()
    self.request_stack["RETR"]=(FTP_STK_DATA, (Z_STACK_PROVIDER, "demo_stackingprovider", "default_rulegroup"))

StackingProvider(name="demo_stackingprovider_socket", backend=RemoteStackingBackend(addrs=(SockAddrUnix('/var/run/zcv/zcv.sock'),)))</synopsis>
        </example>
      </description>
      <metainfo>
        <attributes/>
      </metainfo>
    </class>
    """
    def __init__(self, name, backend):
        """
        <method maturity="stable">
          <summary>Constructor to initialize an instance of the StackingProvider class.
          </summary>
          <description>
            <para>This constructor creates a StackingProvider instance
            and sets the attributes of the instance according to the received arguments.
            </para>
          </description>
          <metainfo>
            <arguments>
              <argument maturity="stable">
                <name>name</name>
                <type>
                  <string/>
                </type>
                <description>Name of the Stacking provider policy. This
                name can be referenced in the service definitions.</description>
              </argument>
              <argument maturity="stable">
                <name>backend</name>
                <type>
                  <class filter="stackingbackend" instance="yes"/>
                </type>
                <description>A configured <link linkend="python.Stack.RemoteStackingBackend">
                RemoteStackingBackend</link> class containing the address
                of the remote stacking backend, e.g.,
                <parameter>RemoteStackingBackend(addrs=(SockAddrInet('192.168.2.3', 1318),))</parameter> or                        <parameter>RemoteStackingBackend(addrs=(SockAddrUnix('/var/run/zcv/zcv.sock'),)).
                </parameter>.
                 </description>
              </argument>
            </arguments>
          </metainfo>
        </method>
        """

        if Globals.stacking_providers.has_key(name):
            raise ValueError, "Duplicate Stacking provider: %s" % name
        Globals.stacking_providers[name] = self
        self.name = name
        self.backend = backend

def getStackingProviderBackend(name):
    """
    <function internal="yes"/>"""
    if name:
        if Globals.stacking_providers.has_key(name):
            return Globals.stacking_providers[name].backend
        else:
            log(None, CORE_POLICY, 3, "No such stacking provider; provider='%s'", (name))
    return None

class AbstractStackingBackend(object):
    """
    <class maturity="stable" abstract="yes" type="stackingbackend">
      <summary>This is an abstract class, currently without any functionality.
      </summary>
      <description>
        <para>This is an abstract class, currently without any functionality.
        </para>
      </description>
      <metainfo>
        <attributes/>
      </metainfo>
    </class>
    """
    def __init__(self):
        """
        <method internal="yes"/>"""
        pass

    def stack(self):
        """
        <method internal="yes"/>"""
        raise IOError, "Unimplemented stacking method"

class RemoteStackingBackend(AbstractStackingBackend):
    """
    <class type="stackingbackend">
      <summary>Constructor to initialize an instance of the RemoteStackingBackend class.
      </summary>
      <description>
        <para>
        This class contains the address of the host that performs the stacked
         operations. It is typically used to access the Zorp Content Vectoring
          Server (ZCV) to perform virus filtering in the traffic. The remote
          backend can be accessed using the TCP protocol or a local socket,
          e.g., <parameter>RemoteStackingBackend(addrs=(SockAddrInet('192.168.2.3', 1318),))</parameter> or                        <parameter>RemoteStackingBackend(addrs=(SockAddrUnix('/var/run/zcv/zcv.sock'),)).
                    </parameter>.
        </para>
      </description>
      <metainfo>
        <attributes/>
      </metainfo>
    </class>
    """
    def __init__(self, addrs):
        """
        <method>
          <summary>
          </summary>
          <description>
            <para>
            </para>
          </description>
          <metainfo>
            <arguments>
              <argument maturity="stable">
                <name>addrs</name>
                <type>
                  <list>
                    <sockaddr/>
                  </list>
                </type>
                <description>The address of the remote backend in
                <link linkend="python.SockAddr.SockAddrInet">SockAddrInet</link> or
                <link linkend="python.SockAddr.SockAddrUnix">SockAddrUnix</link>
                 format. Separate addresses with commas to list more than one address
                 for a backend. Zorp will connect to these addresses in a
                 failover fashion.</description>
              </argument>
            </arguments>
          </metainfo>
        </method>
        """
        super(RemoteStackingBackend, self).__init__()
        self.addrs = addrs
        self.current_host = 0

    def stack(self, stack_info):
        """
        <method internal="yes"/>"""
        for i in range(len(self.addrs)):
            addr = self.addrs[self.current_host]
            self.current_host = (self.current_host + 1) % len(self.addrs)

            try:
                return performStackRemote(addr, stack_info)
            except IOError:
                log(None, CORE_ERROR, 3, "Error performing stack handshake with peer; addr='%s'", str(addr))
        log(None, CORE_DEBUG, 6, "Could not finish handshake with any of the stacking peers;")
        return None
