799 lines
35 KiB
Python
799 lines
35 KiB
Python
#!/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 re
|
|
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)
|
|
|
|
# Throw away bad referers
|
|
bad_referrers = rootDataHolder.getConfigList("yep:badReferrers/yep:host", default = [])
|
|
referer = httpRequestHandler.headers.get("Referer", "")
|
|
if referer:
|
|
for bad in bad_referrers:
|
|
if bad == referer or re.findall(bad, referer):
|
|
httpRequestHandler.headers["Referer"] = "BAD"
|
|
return httpRequestHandler.outputErrorAccessForbidden("")
|
|
|
|
# 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")
|
|
try:
|
|
namespaces.getUri(name)
|
|
except KeyError:
|
|
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__")
|