#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Authentication Server"""

__version__ = '$Revision$'[11:-2]


import getopt
import os
import signal
import sys
import traceback

try:
    from OpenSSL import SSL
except ImportError:
    SSL = None

glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

import glasnost.common.applications as applications
import glasnost.common.context as context
import glasnost.common.tools_new as commonTools

from glasnost.server.tools import *

from glasnost.proxy.DispatcherProxy import callServer


class Gateway(applications.Application):
    applicationName = 'Gateway'
    applicationRole = 'gateway'
    hostName = None
    port = None
    publicPort = None
    rpcServer = None

    def callGateway(self, virtualServerId, clientToken, userToken, serverId,
                    functionName, arguments):
        # Remove the port (if any) from the serverId, because the public port
        # of the gateway is the same as the serverPort of the dispatcher.
        # So we ensure that the Gateway wont call itself endlessly instead of
        # the dispatcher.
        # The removal of the port is also good for security reasons.
        # the same port of the dispatcherId
        realServerId = commonTools.makeApplicationId(
                'glasnost://%s' % extractApplicationHostName(serverId),
                commonTools.extractRole(serverId))
        arguments[0] = realServerId
        dispatcherId = commonTools.extractDispatcherId(virtualServerId)
        context.push(
                _level = 'callGateway',
                applicationToken = clientToken,
                dispatcherId = dispatcherId,
                userToken = userToken,
                )
        try:
            return callServer(realServerId, functionName, arguments)
        finally:
            context.pull(_level = 'callGateway')

    def getServerAccessor(self, virtualServerId, clientToken, userToken,
                          serverId):
        return {
            'isGateway': 1,
            'serverHostName': self.hostName,
            'serverPort': self.publicPort,
            }

    def handleGetopt(self):
        try:
            opts, args = getopt.getopt(
                sys.argv[1:],
                'd:hi:s:x:y:',
                ['destination=', 'export=', 'help', 'import=', 'source=',
                 'system='])
        except getopt.GetoptError:
            self.usage()
            return 2
        if len(args) != 0:
            self.usage()
            return 2
        destinationDispatcherId = None
        doExport = 0
        doImport = 0
        doSystem = 0
        exportDirectoryPath = None
        importDirectoryPath = None
        sourceDispatcherId = None
        for o, a in opts:
            if o in ('-d', '--destination'):
                destinationDispatcherId = a
                if destinationDispatcherId[-1] == '/':
                    destinationDispatcherId = destinationDispatcherId[:-1]
            if o in ('-h', '--help'):
                usage()
                return 0
            if o in ('-i', '--import'):
                importDirectoryPath = a
                doImport = 1
            if o in ('-s', '--source'):
                sourceDispatcherId = a
                if sourceDispatcherId[-1] == '/':
                    sourceDispatcherId = sourceDispatcherId[:-1]
            if o in ('-x', '--export'):
                exportDirectoryPath = a
                doExport = 1
            if o in ('-y', '--system'):
                doSystem = 1
        if doImport or doExport or doSystem:
            return 0
        if sourceDispatcherId and destinationDispatcherId:
            return 0
        return None
        
    def handleSignal(self, signalNumber, stackFrame):
        if signalNumber == signal.SIGINT:
            raise KeyboardInterrupt
        if signalNumber == signal.SIGHUP:
            self.setLogFile()
            return
        if signalNumber == signal.SIGTERM:
            print 'Received signal SIGTERM: exiting.'
            os._exit(0)

    def launch(self):
        applications.Application.launch(self)
        goDaemon()
        self.setLogFile()
        self.startRpcServer()
        self.run()

    def loadConfigOptions(self):
        applications.Application.loadConfigOptions(self)

        configContext = context.get(_level = 'config')

        self.hostName = commonTools.getConfig(
                self.applicationName, 'ServerHostName')
        if not self.hostName:
            self.hostName = 'localhost'

        try:
            self.port = eval(commonTools.getConfig(
                    self.applicationName, 'ServerPort'), {})
        except TypeError:
            if self.port is not None:
                del self.port

        try:
            self.publicPort = eval(commonTools.getConfig(
                    self.applicationName, 'PublicPort'), {})
        except TypeError:
            if self.publicPort is not None:
                del self.publicPort

    def registerPublicMethod(self, methodName, method = None):
        if method is None:
            method = getattr(self, methodName)
        self.rpcServer.add_method(methodName, method, help = method.__doc__)

    def registerPublicMethods(self):
        self.registerPublicMethod('callGateway')
        self.registerPublicMethod('getServerAccessor')

    def run(self):
        signal.signal(signal.SIGINT, self.handleSignal)
        signal.signal(signal.SIGHUP, self.handleSignal)
        signal.signal(signal.SIGTERM, self.handleSignal)
        self.rpcServer.baseContext = context.get()
        while 1:
            try:
                self.rpcServer.serve_forever()
            except KeyboardInterrupt:
                break
            except:
                traceback.print_exc()
        del self.rpcServer.baseContext

    def startRpcServer(self):
        if SSL:
            from glasnost.server.xmlrpcServer import SecureXmlRpcThreadingServer

            certificatePath = '/etc/%s/server.pem' % glasnost.applicationName
            certificateAutorityPath = '/etc/%s/ca.pem' \
                                      % glasnost.applicationName

            ctx = SSL.Context(SSL.SSLv23_METHOD)
            ctx.set_options(SSL.OP_NO_SSLv2)
            ctx.use_privatekey_file ('/etc/%s/server.pkey' % glasnost.applicationName)
            ctx.use_certificate_file('/etc/%s/server.cert' % glasnost.applicationName)

            self.rpcServer = SecureXmlRpcThreadingServer(
                    ('', self.port), sslContext = ctx)
        else:
            from glasnost.server.xmlrpcServer import XmlRpcThreadingServer
            self.rpcServer = XmlRpcThreadingServer(('', self.port))

        self.registerPublicMethods()
        # Don't call registerServer, because the gateway can only be called
        # from the outside, not from the other Glasnost servers.

    def usage(self):
        print 'Usage: %s ' % self.applicationName


gateway = Gateway()


if __name__ == "__main__":
    gateway.launch()

