#!/usr/bin/env python
# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# 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.


"""HTTP Server"""


import __builtin__
import base64
import Cookie
import errno
import gettext
import grp
import locale
import logging
from optparse import OptionParser
import os
import pwd
import sys
import urllib

import libxml2

import expression
import configurations
import dataholders
import elements
import environs
import faults
import filesystems
import html
import http
import httpstatuses
import logs
import locations
import metadatas
import modules
import namespaces
import sessions
import stations
import strings
import stylesheets
import submissions
import things
import webdav
import xmlschemas
import xpaths
import xslt


def goDaemon(nochdir = 0, noclose = 0, user = None, group = None):
    """Run server as INIT daemon

    See also http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
    for another code to create a daemon.
    """

    if user:
        if user.isdigit():
            user = int(user)
        else:
            try:
                user = pwd.getpwnam(user)[2]
            except KeyError:
                user = None

    if group:
        if group.isdigit():
            group = int(group)
        else:
            try:
                group = grp.getgrnam(group)[2]
            except KeyError:
                group = None
        
    if user and user != os.getuid():
        try:
            os.setuid(user)
        except OSError:
            logs.warning("Failed to setuid() to %s" % user)
    if group and group != os.getgid():
        try:
            os.setgid(group)
        except OSError:
            logs.warning("Failed to setgid() to %s" % group)
    
    if os.fork():
        os._exit(0)
    os.setsid()
    if nochdir == 0:
        os.chdir('/')
    if noclose == 0:
        fp = open('/dev/null', 'rw')
        sys.stdin = sys.__stdin__ = fp
        sys.stdout = sys.__stdout__ = fp
        sys.stderr = sys.__stderr__ = fp
        del fp
    if os.fork():
        os._exit(0)


class Application(object):
    xpathContext = None

    def handleHttpCommand(self, httpRequestHandler):
        virtualHosts = environs.getVar("virtualHosts")
        try:
            requestedHostName = httpRequestHandler.headers["Host"].split(":")[0]
        except KeyError:
            requestedHostName = ''
        for virtualHost in virtualHosts:
            if virtualHost.getServerName() == requestedHostName:
                break
        else:
            # handle serverAlias
            for virtualHost in virtualHosts:
                if requestedHostName in virtualHost.getServerAliases():
                    if not virtualHost.getPort() in (80, 443):
                        break # we only redirect on standard ports
                    httpRequestHandler.send_response(301, "Moved Permanently")
                    httpRequestHandler.send_header("Location",
                        environs.getVar("httpServer").uriScheme + '://' + \
                        virtualHost.getServerName() + httpRequestHandler.path)
                    httpRequestHandler.end_headers()
                    httpRequestHandler.wfile.write("Moved")
                    return
            # nothing else, take the first one
            virtualHost = virtualHosts[0]
        hostName = virtualHost.getServerName() or httpRequestHandler.headers["Host"]

        # Needed by talkback.
        environs.setVar("virtualHost", virtualHost)

        environs.push(
            _level = "handleHttpCommand",
            canUseCookie = False,
            cookie = None,
            currentStation = None,
            currentDirectoryPath = None,
            httpAuthenticationLogoutTrick = False,
            httpQuery = None,
            httpRequestHandler = httpRequestHandler,
            httpScriptDirectoryPath = None,
            languageSetInUrl = False,
            readLanguages = None,
            rootStation = None,
            session = None,
            submission = None,
            testCookieSupport = False,
            uriAuthority = hostName,
            uriScheme = environs.getVar("httpServer").uriScheme,
            user = None,
            )
        try:
            splitedPath = httpRequestHandler.path.split("?")

            httpPath = splitedPath[0]
            while len(httpPath) > 1 and httpPath[-1] == "/":
                httpPath = httpPath[:-1]
            if not httpPath:
                httpPath = "/"

            if len(splitedPath) >= 2:
                httpQuery = splitedPath[1]
                if not httpQuery:
                    httpQuery = ""
            else:
                httpQuery = ""
            environs.setVar("httpQuery", httpQuery)

            httpScriptDirectoryPath = "/"
            if httpScriptDirectoryPath == "/":
                httpInternPath = httpPath
            else:
                httpInternPath = httpPath[len(httpScriptDirectoryPath):]
            uriPathFragments = [urllib.unquote(name) for name in httpInternPath.split("/") if name]
            inRawMode = False
            if uriPathFragments and uriPathFragments[0] == "raw":
                inRawMode = uriPathFragments[0]
                uriPathFragments = uriPathFragments[1:]
                if httpScriptDirectoryPath[-1] == "/":
                    httpScriptDirectoryPath = "%s%s" % (httpScriptDirectoryPath, inRawMode)
                else:
                    httpScriptDirectoryPath = "%s/%s" % (httpScriptDirectoryPath, inRawMode)
            environs.setVar("httpScriptDirectoryPath", httpScriptDirectoryPath)

            # Setup root data holder.
            documentRootAbsolutePath = virtualHost.getConfigAbsolutePath("yep:documentRoot")
            fileSystem = filesystems.PartialFileSystem(documentRootAbsolutePath)
            uriPathFragment = environs.getVar("httpScriptDirectoryPath")[1:]
            rootDataHolder = dataholders.DataHolder(
                pathFragment = documentRootAbsolutePath,
                uriPathFragment = uriPathFragment,
                uriScheme = environs.getVar("uriScheme"),
                uriAuthority = environs.getVar("uriAuthority"),
                isRootElder = True,
                containedFileSystem = fileSystem)
            environs.setVar("rootStation", rootDataHolder)
            environs.setVar("currentStation", rootDataHolder)

            # Get query, headers and form variables in a submission object.
            submission = submissions.readSubmission(httpRequestHandler)
            environs.setVar("submission", submission)

            session = None
            sessionToken = None
            user = None
            
            # Handle X509 certificate authentication.
            if environs.getVar("peerCertificate", default = None):
                cert = environs.getVar("peerCertificate")
                serial = "%x" % int(cert.get_serial_number())
                from expression.modules.x509accounts import X509Account
                try:
                    accountHolder = rootDataHolder.walkToLocation("/accounts/x509/%s" % serial)
                except faults.PathNotFound:
                    logs.info("Wrong serial number = %s" % serial)
                    accountHolder = None

                if accountHolder:
                    account = accountHolder.getRootElement()
                    if not isinstance(account, X509Account):
                        logs.info("Bad X509 account file (%r)" % account)
                        return httpRequestHandler.outputErrorUnauthorized(httpPath)

                    user = account.getUser()
                    if user:
                        sessionToken = user.getSessionToken()
                    else:
                        logs.warning('Account "%s" without user.' % account)
                        return httpRequestHandler.outputErrorUnauthorized(httpPath)

                if sessionToken:
                    session = sessions.retrieveSession(sessionToken)
                    if session is None:
                        sessionToken = None
                        user.deleteSessionToken()
                        # Don't call user.getDocument().save() now, because the session
                        # will be retrieved (from cookie or sessionToken submission field)
                        # or created. So user/@sessionToken will be updated.
                    else:
                        session.setPublishToken("true")

            # Handle HTTP authentication.
            authorization = httpRequestHandler.headers.get("authorization")
            if submission.getField("_action") == "login" and not authorization \
                   and rootDataHolder.getConfigBoolean("yep:useHttpAuthentication",
                                                       default = False):
                # Ask for HTTP authentication.
                return httpRequestHandler.outputErrorUnauthorized(httpPath)
            if submission.getField("_action") == "logout" and authorization:
                # Since HTTP authentication provides no way to logout,  we send a status
                # Unauthorized to force the user to press the cancel button. But instead of
                # sending an error page immediately, we send the real page, so the user will see
                # the page instead of an error message.
                authorization = None
                environs.setVar("httpAuthenticationLogoutTrick", True)
            if authorization:
                try:
                    authenticationScheme, credentials = authorization.split(None, 1)
                except ValueError:
                    return httpRequestHandler.outputErrorUnauthorized(httpPath)
                authenticationScheme = authenticationScheme.lower()
                if authenticationScheme == "basic":
                    loginAndPassword = base64.decodestring(credentials)
                    try:
                        login, password = loginAndPassword.split(":", 1)
                    except:
                        login = loginAndPassword
                        password = ""
                    logs.debug('Basic authentication: login = "%s" / password = "%s"' % (
                        login, password))
                    if password:
                        import expression.modules.passwordaccounts as passwordaccounts
                        try:
                            accountHolder = rootDataHolder.walkToLocation(
                                "/accounts/passwords/%s" % strings.simplify(login))
                        except faults.PathNotFound:
                            logs.info("Wrong login = %s" % login)
                            return httpRequestHandler.outputErrorUnauthorized(httpPath)
                        account = accountHolder.getRootElement()
                        if not isinstance(account, passwordaccounts.PasswordAccount):
                            logs.info("Bad password account file (%r)" % account)
                            return httpRequestHandler.outputErrorUnauthorized(httpPath)

                        if password != account.password:
                            logs.debug("Wrong password = %s (instead of %s)" % (
                                password, account.password))
                            return httpRequestHandler.outputErrorUnauthorized(httpPath)
                        user = account.getUser()
                        if user:
                            sessionToken = user.getSessionToken()
                        else:
                            logs.warning('Account "%s" without user.' % account)
                            return httpRequestHandler.outputErrorUnauthorized(httpPath)
                        if sessionToken:
                            session = sessions.retrieveSession(sessionToken)
                            if session is None:
                                sessionToken = None
                                user.deleteSessionToken()
                                # Don't call user.getDocument().save() now, because the session
                                # will be retrieved (from cookie or sessionToken submission field)
                                # or created. So user/@sessionToken will be updated.
                            else:
                                # For security reasons, we want to minimize the publication of
                                # session token (it is better not to store it in a cookie or in
                                # URLs).
                                if session.publishToken:
                                    del session.publishToken
                    elif login:
                        # No password was given. Assume login contains a session token.
                        # TODO: sanity chek on login
                        session = sessions.retrieveSession(login)
                        if session is not None:
                            account = session.getAccount()
                            if account is not None:
                                user = account.getUser()
                                if user is not None \
                                       and user.getSessionToken() != session.getToken():
                                    # Sanity check.
                                    user.setSessionToken(session.getToken())
                                    user.getDocument().save()
                else:
                    logs.info("Unknown authentication scheme = %s" % authenticationScheme)
                    return httpRequestHandler.outputErrorUnauthorized(httpPath)

            # Handle use of cookies, session and user.
            cookie = None
            cookieContent = {}
            if httpRequestHandler.headers.has_key("Cookie"):
                logs.debug("Cookie received:")
                cookie = Cookie.SimpleCookie(
                    httpRequestHandler.headers["Cookie"])
                for k, v in cookie.items():
                    cookieContent[k] = v.value
                    logs.debug("  %s = %s" % (k, cookieContent[k]))
            environs.setVar("cookie", cookie)

            sessionToken = None
            sessionTokenInCookie = False
            if submission.hasField("sessionToken"):
                sessionToken = submission.getField("sessionToken")
                if not sessionToken:
                    sessionToken = None
                if session is not None and sessionToken != session.token:
                    sessionToken = None
            if cookieContent.has_key("sessionToken"):
                cookieSessionToken = cookieContent["sessionToken"]
                if cookieSessionToken:
                    if session is None or cookieSessionToken == session.token:
                        if sessionToken is None:
                            sessionToken = cookieSessionToken
                        if cookieSessionToken == sessionToken:
                            sessionTokenInCookie = True
            canUseCookie = True
            if session is None and sessionToken is not None:
                session = sessions.retrieveSession(sessionToken)
                if session is None:
                    sessionToken = None
                    sessionTokenInCookie = False
                else:
                    if user is None:
                        # import expression.modules.passwordaccounts as passwordaccounts
                        account = session.getAccount(acceptOnlyAccount = False)
                        if account is not None:
                            user = account.getUser()
                            if user is not None and user.getSessionToken() != sessionToken:
                                # Sanity check.
                                user.setSessionToken(session.getToken())
                                user.getDocument().save()
                    else:
                        # The user has been authenticated (using HTTP authentication), but the
                        # associated session didn't exist (or was too old, or...). So, update
                        # its sessionToken.
                        user.setSessionToken(session.getToken())
                        user.getDocument().save()
                        # For security reasons, we want to minimize the publication of session
                        # token (it is better not to store it in a cookie or in URLs).
                        if session.publishToken:
                            del session.publishToken
            if session is None and user is not None:
                # The user has been authenticated (using HTTP authentication), but the session
                # doesn't exist yet (or was too old, or...). Create a new session.
                session = sessions.getOrCreateSession()
                # For security reasons, we want to minimize the publication of session
                # token (it is better not to store it in a cookie or in URLs).
                # session.publishToken = False # False is the default value.
                session.setAccountAbsolutePath(account.getAbsolutePath())
                user.setSessionToken(session.getToken())
                user.getDocument().save()
            if user is not None:
                environs.setVar("user", user)
                logs.debug("User: %s" % user.simpleLabel)
            if session is not None:
                if not sessionTokenInCookie:
                    # The sessionToken is valid but is not stored in the cookie. So, don't try to
                    # use cookie.
                    canUseCookie = False
                environs.setVar("session", session)
                logs.debug("Session: %s" % session.token)
            if canUseCookie:
                environs.setVar("canUseCookie", canUseCookie)

            # Look for the right languages to use for HTTP answer.
            languages = []
            if user is not None:
                userLanguage = user.getLanguage()
                if userLanguage is not None:
                    languages = [userLanguage]
            if  not languages \
                    and httpRequestHandler.headers.has_key("Accept-Language"):
                languages = [
                    language.strip()[:2]
                    for language in httpRequestHandler.headers[
                        "Accept-Language"].split(",")
                    if language.strip()]
            virtualHostLanguage = virtualHost.getConfigString("yep:language", default = "en")
            if virtualHostLanguage not in languages:
                languages.append(virtualHostLanguage)
            environs.setVar("readLanguages", languages)
            self.setGettextLanguage()

            try:
                try:
                    if not rootDataHolder.checkAccess(
                            uriPathFragments, httpRequestHandler.command):
                        raise faults.PathNotFound("/".join(uriPathFragments))
                    if inRawMode:
                        if not rootDataHolder.isContentAccessAuthorized(inRawMode, None):
                            raise faults.PathUnauthorized("/".join([inRawMode] + uriPathFragments))
                        rootDataHolder._inRawMode = True
                    rootDataHolder.walk(uriPathFragments, httpRequestHandler.command)
                except faults.BadRequest, fault:
                    if environs.getVar("debug"):
                        logs.exception(str(fault))
                    httpRequestHandler.outputErrorBadRequest(fault.reason, station = fault.station)
                except faults.PathForbidden, fault:
                    if environs.getVar("debug"):
                        logs.exception(str(fault))
                    httpRequestHandler.outputErrorAccessForbidden(
                        fault.path, station = fault.station)
                except faults.PathNotFound, fault:
                    if environs.getVar("debug"):
                        logs.exception(str(fault))
                    httpRequestHandler.outputErrorNotFound(fault.path, station = fault.station)
                except faults.PathUnauthorized, fault:
                    if environs.getVar("debug"):
                        logs.exception(str(fault))
                    # Output an Unauthorized status only when the user is not yet logged in,
                    # because we assume each user has only one account.
                    if environs.getVar("user") is None:
                        httpRequestHandler.outputErrorUnauthorized(
                            fault.path, station = fault.station)
                    else:
                        httpRequestHandler.outputErrorAccessForbidden(
                            fault.path, station = fault.station)
                except:
                    httpRequestHandler.outputUnknownException()
            finally:
                session = environs.getVar("session")
                if session is not None:
                    sessionDocument = session.getDocument()
                    if sessionDocument.isDirty:
                        sessionDocument.save()
        finally:
            environs.pull(_level = "handleHttpCommand")

    def launch(self):
            # We need to use only one libxml2.xpathContext, because the libxml2
            # Python binding limits the number of registered XPath functions to
            # 10, even if they are registered in different contexts.
            #
            # Other problem: A segmentation fault occurs when setContextDoc is
            # called and the previous context doc was None. So we can't do:
            # self.xpathContext = libxml2.xpathContext()
            dummyDoc = libxml2.newDoc("1.0")
            dummyDoc.newTextChild(None, "dummy", None)
            self.xpathContext = dummyDoc.xpathNewContext()
            for ns in namespaces.nsList:
                self.xpathContext.xpathRegisterNs(ns.name, ns.uri)
            xpaths.registerFunctions(self.xpathContext)
            xslt.registerFunctions()

    def setGettextLanguage(self):
        virtualHost = environs.getVar("virtualHost")
        configuration = environs.getVar("configuration")
        readLanguages = environs.getVar("readLanguages")
        domains = virtualHost.getDomains() + [expression.applicationName]
        locales = configuration.getLocales()
        localesPath = configuration.getLocalesPath() or "/usr/share/locale"
        language = readLanguages[0]
        for l in readLanguages:
            if l in locales:
                language = l
                break
        try:
            locale.setlocale(locale.LC_COLLATE, (language, None))
        except locale.Error:
            locale.setlocale(locale.LC_COLLATE, "C")
        translation = gettext.NullTranslations()
        for domain in domains:
            try:
                domainTranslation = gettext.translation(
                    domain, localesPath, [language])
                logs.debug("using translations in language %s for domain %s" % (language, domain))
            except IOError:
                continue
            if not hasattr(domainTranslation, "_catalog"):
                continue
            if translation.__class__.__name__ == "NullTranslations":
                translation = domainTranslation
                continue
            translation._catalog.update(domainTranslation._catalog)
        __builtin__.__dict__["_"] = translation.gettext

    def stop(self):
            xslt.unregisterFunctions()
            xpaths.unregisterFunctions(self.xpathContext)
            self.xpathContext.xpathFreeContext()
            del self.xpathContext


# Main.


def main():
    parser = OptionParser(version = "%prog 0.1")
    parser.add_option("-f",
            dest = "configurationFilePath",
            help = "specify an alternate configuration file",
            metavar = "FILE",
            default = "/etc/expression/config.xml")
    parser.add_option("-l",
            dest = "logLevel",
            help = "specify log level (debug, info, warning, error, critical)",
            metavar = "LEVEL",
            default = "error")
    parser.add_option("--debug",
            dest = "debugMode",
            action = "store_true",
            default = False,
            help = "set debug mode")
    parser.add_option("-F",
            dest = "daemonMode",
            action = "store_false",
            default = True,
            help = "run main process in foreground, for process supervisors")
    parser.add_option("--no-email",
            dest = "sendEmails",
            action = "store_false",
            default = True,
            help = "don't send emails (they are logged instead)")

    (options, args) = parser.parse_args()

    if options.logLevel.upper() not in logging._levelNames.keys():
        raise Exception("Unknown log level (%s)" % options.logLevel)

    # Configure libxml2 to be thread safe.
    libxml2.initParser()

    # One more libxml2 idiosyncrasy: After having RTFM for function xmlKeepBlanksDefault, it
    # seems that it is better to never call it, because, calling it causes XMLSec1 and/or Lasso to
    # fail when checking signatures. And we haven't found a clean way for Lasso to save and restore
    # the following variables when dumping XML.
    # By default:
    #   xmlKeepBlanksDefaultValue = 1
    #   xmlIndentTreeOutput = 1
    # After calling libxml2.keepBlanksDefault(0):
    #   xmlKeepBlanksDefaultValue = 0
    #   xmlIndentTreeOutput = 1
    # After calling libxml2.keepBlanksDefault(1):
    #   xmlKeepBlanksDefaultValue = 1
    #   xmlIndentTreeOutput = 0
    #
    # libxml2.keepBlanksDefault(0)

    environs.push(
        _level = "__main__",
        application = None,
        configuration = None,
        debug = options.debugMode,
        httpPort = None,
        httpServer = None,
        httpsPort = None,
        logger = None,
        sendEmails = options.sendEmails,
        virtualHosts = None,
        )
    try:
        application = Application()
        application.launch()
        environs.setVar("application", application)
    
        # Build configuration from configuration file.
        configurationAbsolutePath = os.path.abspath(options.configurationFilePath)
        fileSystem = filesystems.PartialFileSystem(configurationAbsolutePath)
        try:
            configurationHolder = dataholders.DataHolder(
                pathFragment = configurationAbsolutePath, mimeType = "text/xml",
                isRootElder = True, containedFileSystem = fileSystem)
        except IOError, error:
            if error.errno == errno.ENOENT:
                print >> sys.stderr, "No such configuration file:", options.configurationFilePath
                sys.exit(1)
            raise
        configuration = configurationHolder.getRootElement()
        if not isinstance(configuration, configurations.Configuration):
            print >> sys.stderr, "Bad configuration file %s; aborting" % configuration
            sys.exit(1)
        environs.setVar("configuration", configuration)

        # Configure root logger.
        logger = logging.getLogger()
        if options.debugMode and not options.daemonMode:
            handler = logging.StreamHandler(sys.stdout)
        else:
            handler = logging.FileHandler(configuration.getConfigAbsolutePath(
                "yep:logFilePath", default = "/dev/null"))
        formatter = logging.Formatter('%(asctime)s %(levelname)-9s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.setLevel(logging._levelNames[options.logLevel.upper()])
        environs.setVar("logger", logger)

        sys.path.extend(configuration.getPythonAbsolutePaths())
        sys.path.extend(
            ['/usr/local/lib/expression/modules',
             '/usr/lib/expression/modules'])

        for moduleName in configuration.getPythonModuleNames():
            try:
                module = __import__(moduleName)
            except ImportError:
                logs.error("Failed to load module %s; skipping" % moduleName)
                raise

        for namespace in configuration.evaluateXpath("yep:namespace"):
            name, uri = namespace.prop("name"), namespace.prop("uri")
            if not namespaces.getUri(name):
                ns = namespaces.Namespace(name, uri)
            application.xpathContext.xpathRegisterNs(name, uri)

        allVirtualHosts = [
            virtualHost for virtualHost in configuration.getVirtualHosts()]
            # Needed because virtualHosts is not a list
            #   TODO but it should be made Iterable
        if not allVirtualHosts:
            logs.critical("No virtual host defined; aborting")
            sys.exit(1)

        virtualHostsByIpAddressAndPortCouple = {}
        accessLogFiles = {}
        for virtualHost in allVirtualHosts:
            if not virtualHost.getPort():
                logs.warning("Virtual host without port; skipping")
                continue
            if not virtualHost.getConfigBaseRelativePath("yep:documentRoot", default = None):
                logs.warning("Virtual host without document root; skipping.")
                continue
            ipAddressAndPort = (virtualHost.getIpAddress(), virtualHost.getPort())
            if not virtualHostsByIpAddressAndPortCouple.has_key(ipAddressAndPort):
                virtualHostsByIpAddressAndPortCouple[ipAddressAndPort] = []
            virtualHostsByIpAddressAndPortCouple[ipAddressAndPort].append(virtualHost)
            accessLogFilePath = virtualHost.getConfigAbsolutePath("yep:accessLogFilePath",
                default = None)
            if not accessLogFilePath:
                accessLogFilePath = configuration.getConfigAbsolutePath("yep:accessLogFilePath",
                    default = None)
            if accessLogFilePath:
                if not accessLogFiles.has_key(accessLogFilePath):
                    fd = open(accessLogFilePath, "a", 1)
                    accessLogFiles[accessLogFilePath] = fd
                virtualHost.accessLogFile = accessLogFiles[accessLogFilePath]

            if virtualHost.isSsl:
                assert virtualHost.brotherVirtualHost is None
                for nonSslVirtualHost in allVirtualHosts:
                    if nonSslVirtualHost == virtualHost:
                        continue
                    if nonSslVirtualHost.isSsl:
                        continue
                    if nonSslVirtualHost.getIpAddress() == virtualHost.getIpAddress() \
                            and nonSslVirtualHost.getServerName() == virtualHost.getServerName():
                        assert nonSslVirtualHost.brotherVirtualHost is None
                        nonSslVirtualHost.brotherVirtualHost = virtualHost
                        virtualHost.brotherVirtualHost = nonSslVirtualHost
                        break
    
        if not virtualHostsByIpAddressAndPortCouple:
            logs.critical("No correct virtual host defined; aborting")
            sys.exit(1)

        if options.daemonMode:
            goDaemon(
                nochdir = 1, user = configuration.getConfigString("yep:user", default = None),
                group = configuration.getConfigString("yep:group", default = None))
    
        httpPort = None
        httpsPort = None
        for serverIpAddressAndPortCouple, virtualHosts \
                in virtualHostsByIpAddressAndPortCouple.items():
            if os.fork():
                continue
            httpServerIpAddressAndPortCouple = serverIpAddressAndPortCouple
            virtualHost = virtualHosts[0]
            if virtualHost.isSsl:
                HttpRequestHandler = http.HttpsRequestHandler
                httpServer = http.HttpsServer(
                    httpServerIpAddressAndPortCouple, HttpRequestHandler,
                    virtualHost.getConfigAbsolutePath("yep:sslCertificateKeyFile"),
                        # Server private key
                    virtualHost.getConfigAbsolutePath("yep:sslCertificateFile"),
                        # Server certificate
                    virtualHost.getConfigAbsolutePath("yep:sslCACertificateFile", None),
                        # Clients certification authority certificate
                    virtualHost.getConfigAbsolutePath("yep:sslCertificateChainFile", None),
                        # see mod_ssl, ssl_engine_init.c, line 852
                    virtualHost.getConfigString("yep:sslVerifyClient", None),
                        # http://www.modssl.org/docs/2.1/ssl_reference.html#ToC13
                    )
                httpsPort = httpServerIpAddressAndPortCouple[1]
            else:
                HttpRequestHandler = http.HttpRequestHandler
                httpServer = http.HttpServer(
                    httpServerIpAddressAndPortCouple, HttpRequestHandler)
                httpPort = httpServerIpAddressAndPortCouple[1],
            break
        else:
            pidFileAbsolutePath = configuration.getConfigAbsolutePath(
                "yep:pidFilePath", default = None)
            if pidFileAbsolutePath is None:
                logs.info("No PID file specified; PID is %s" % os.getpid())
            else:
                pidFile = file(pidFileAbsolutePath, "w")
                pidFile.write(str(os.getpid()))
                pidFile.close()
            while 1:
                try:
                    os.wait()
                except KeyboardInterrupt:
                    logger.info("Exiting on KeyboardInterrupt")
                    break
            os._exit(0)

        environs.setVar("httpServer", httpServer)
        environs.setVar("httpPort", httpPort)
        environs.setVar("httpsPort", httpsPort)
        environs.setVar("virtualHosts", virtualHosts)

        HttpRequestHandler.application = application
        HttpRequestHandler.baseEnviron = environs.get()
        httpServer.application = application
        if isinstance(httpServer, http.HttpsServer):
            logger.info("Serving HTTPS on %s port %s..." % httpServer.socket.getsockname())
        else:
            logger.info("Serving HTTP on %s port %s..." % httpServer.socket.getsockname())
        try:
            httpServer.serve_forever()
        except KeyboardInterrupt:
            pass
        application.stop()
        del HttpRequestHandler.baseEnviron
    finally:
        environs.pull(_level = "__main__")
