############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Startup package. Responsible for startup configuration of Zope """ import logging import os import sys import socket from re import compile from socket import gethostbyaddr try: import twisted.internet.reactor _use_twisted = True except ImportError: _use_twisted = True import ZConfig from ZConfig.components.logger import loghandler logger = logging.getLogger("Zope") started = False def get_starter(): check_python_version() if sys.platform[:3].lower() == "win": return WindowsZopeStarter() else: return UnixZopeStarter() def start_zope(cfg, debug_handler): """The function called by run.py which starts a Zope appserver.""" global started if started: # Don't allow any code to call start_zope() twice. return starter = get_starter() starter.setConfiguration(cfg) starter.prepare() started = True try: starter.run() finally: started = False class ZopeStarter: """This is a class which starts a Zope server. Making it a class makes it easier to test. """ def __init__(self): self.event_logger = logging.getLogger() # We log events to the root logger, which is backed by a # "StartupHandler" log handler. The "StartupHandler" buffers # log messages. When the "real" loggers are set up, we flush # accumulated messages in StartupHandler's buffers to the real # logger. formatter = logging.Formatter( "%(asctime)s %(levelname)s %(name)s %(message)s", "%Y-%m-%d %H:%M:%S") self.debug_handler = loghandler.StreamHandler() self.debug_handler.setFormatter(formatter) self.debug_handler.setLevel(logging.WARN) self.startup_handler = loghandler.StartupHandler() self.event_logger.addHandler(self.debug_handler) self.event_logger.addHandler(self.startup_handler) def setConfiguration(self, cfg): self.cfg = cfg def prepare(self): self.setupInitialLogging() self.setupLocale() self.setupSecurityOptions() self.setupPublisher() # Start ZServer servers before we drop privileges so we can bind to # "low" ports: self.setupZServer() self.setupServers() # drop privileges after setting up servers self.dropPrivileges() self.makeLockFile() self.makePidFile() self.setupInterpreter() self.startZope() from App.config import getConfiguration config = getConfiguration() if not config.twisted_servers: self.registerSignals() # emit a "ready" message in order to prevent the kinds of emails # to the Zope maillist in which people claim that Zope has "frozen" # after it has emitted ZServer messages. logger.info('Ready to handle requests') self.setupFinalLogging() def run(self): # the mainloop. try: from App.config import getConfiguration config = getConfiguration() import ZServer if config.twisted_servers and config.servers: raise ZConfig.ConfigurationError( "You can't run both ZServer servers and twisted servers.") if config.twisted_servers: if not _use_twisted: raise ZConfig.ConfigurationError( "You do not have twisted installed.") twisted.internet.reactor.run() # Storing the exit code in the ZServer even for twisted, # but hey, it works... sys.exit(ZServer.exit_code) else: import Lifetime Lifetime.loop() sys.exit(ZServer.exit_code) finally: self.shutdown() def shutdown(self): self.unlinkLockFile() self.unlinkPidFile() # XXX does anyone actually use these three? def info(self, msg): logger.info(msg) def panic(self, msg): logger.critical(msg) def error(self, msg): logger.error(msg) def setupPublisher(self): import Globals import ZPublisher.HTTPRequest import ZPublisher.Publish Globals.DevelopmentMode = self.cfg.debug_mode ZPublisher.Publish.set_default_debug_mode(self.cfg.debug_mode) ZPublisher.Publish.set_default_authentication_realm( self.cfg.http_realm) if self.cfg.publisher_profile_file: filename = self.cfg.publisher_profile_file ZPublisher.Publish.install_profiling(filename) if self.cfg.trusted_proxies: # DM 2004-11-24: added host name mapping (such that examples in conf file really have a chance to work mapped = [] for name in self.cfg.trusted_proxies: mapped.extend(_name2Ips(name)) ZPublisher.HTTPRequest.trusted_proxies = tuple(mapped) def setupSecurityOptions(self): import AccessControl AccessControl.setImplementation( self.cfg.security_policy_implementation) AccessControl.setDefaultBehaviors( not self.cfg.skip_ownership_checking, not self.cfg.skip_authentication_checking, self.cfg.verbose_security) def setupLocale(self): # set a locale if one has been specified in the config if not self.cfg.locale: return # workaround to allow unicode encoding conversions in DTML import codecs dummy = codecs.lookup('iso-8859-1') locale_id = self.cfg.locale if locale_id is not None: try: import locale except: raise ZConfig.ConfigurationError( 'The locale module could not be imported.\n' 'To use localization options, you must ensure\n' 'that the locale module is compiled into your\n' 'Python installation.' ) try: locale.setlocale(locale.LC_ALL, locale_id) except: raise ZConfig.ConfigurationError( 'The specified locale "%s" is not supported by your' 'system.\nSee your operating system documentation for ' 'more\ninformation on locale support.' % locale_id ) def setupZServer(self): # Increase the number of threads import ZServer ZServer.setNumberOfThreads(self.cfg.zserver_threads) ZServer.CONNECTION_LIMIT = self.cfg.max_listen_sockets def setupServers(self): socket_err = ( 'There was a problem starting a server of type "%s". ' 'This may mean that your user does not have permission to ' 'bind to the port which the server is trying to use or the ' 'port may already be in use by another application. ' '(%s)' ) servers = [] for server in self.cfg.servers: # create the server from the server factory # set up in the config try: servers.append(server.create()) except socket.error,e: raise ZConfig.ConfigurationError(socket_err % (server.servertype(),e[1])) self.cfg.servers = servers def dropPrivileges(self): return dropPrivileges(self.cfg) def getLoggingLevel(self): if self.cfg.eventlog is None: level = logging.INFO else: # get the lowest handler level. This is the effective level # level at which which we will spew messages to the console # during startup. level = self.cfg.eventlog.getLowestHandlerLevel() return level def setupConfiguredLoggers(self): # Must happen after ZopeStarter.setupInitialLogging() self.event_logger.removeHandler(self.startup_handler) if self.cfg.zserver_read_only_mode: # no log files written in read only mode return if self.cfg.eventlog is not None: self.cfg.eventlog() if self.cfg.access is not None: self.cfg.access() if self.cfg.trace is not None: self.cfg.trace() # flush buffered startup messages to event logger if self.cfg.debug_mode: self.event_logger.removeHandler(self.debug_handler) self.startup_handler.flushBufferTo(self.event_logger) self.event_logger.addHandler(self.debug_handler) else: self.startup_handler.flushBufferTo(self.event_logger) def setupInitialLogging(self): if self.cfg.debug_mode: self.debug_handler.setLevel(self.getLoggingLevel()) else: self.event_logger.removeHandler(self.debug_handler) self.debug_handler = None def startZope(self): # Import Zope import Zope2 Zope2.startup() def makeLockFile(self): if not self.cfg.zserver_read_only_mode: # lock_file is used for the benefit of zctl-like systems, so they # can tell whether Zope is already running before attempting to # fire it off again. # # We aren't concerned about locking the file to protect against # other Zope instances running from our CLIENT_HOME, we just # try to lock the file to signal that zctl should not try to # start Zope if *it* can't lock the file; we don't panic # if we can't lock it. # we need a separate lock file because on win32, locks are not # advisory, otherwise we would just use the pid file from Zope2.Startup.misc.lock_file import lock_file lock_filename = self.cfg.lock_filename try: if os.path.exists(lock_filename): os.unlink(lock_filename) self.lockfile = open(lock_filename, 'w') lock_file(self.lockfile) self.lockfile.write(str(os.getpid())) self.lockfile.flush() except IOError: pass def makePidFile(self): if not self.cfg.zserver_read_only_mode: # write the pid into the pidfile if possible try: if os.path.exists(self.cfg.pid_filename): os.unlink(self.cfg.pid_filename) f = open(self.cfg.pid_filename, 'w') f.write(str(os.getpid())) f.close() except IOError: pass def unlinkPidFile(self): if not self.cfg.zserver_read_only_mode: try: os.unlink(self.cfg.pid_filename) except OSError: pass def unlinkLockFile(self): if not self.cfg.zserver_read_only_mode: try: self.lockfile.close() os.unlink(self.cfg.lock_filename) except OSError: pass def setupInterpreter(self): """ make changes to the python interpreter environment """ sys.setcheckinterval(self.cfg.python_check_interval) class WindowsZopeStarter(ZopeStarter): def registerSignals(self): from Signals import Signals Signals.registerZopeSignals([self.cfg.eventlog, self.cfg.access, self.cfg.trace]) def setupInitialLogging(self): ZopeStarter.setupInitialLogging(self) self.setupConfiguredLoggers() def setupFinalLogging(self): pass class UnixZopeStarter(ZopeStarter): def registerSignals(self): from Signals import Signals Signals.registerZopeSignals([self.cfg.eventlog, self.cfg.access, self.cfg.trace]) def setupInitialLogging(self): ZopeStarter.setupInitialLogging(self) level = self.getLoggingLevel() self.startup_handler.setLevel(level) # set the initial logging level (this will be changed by the # zconfig settings later) self.event_logger.setLevel(level) def setupFinalLogging(self): self.setupConfiguredLoggers() def check_python_version(): # check for Python version python_version = sys.version.split()[0] optimum_version = '2.3.4' if python_version < '2.3.4': raise ZConfig.ConfigurationError( 'Invalid python version: %s, the optimal version is %s or higher' % (python_version, optimum_version)) def dropPrivileges(cfg): # Drop root privileges if we have them and we're on a posix platform. # This needs to be a function so it may be used outside of Zope # appserver startup (e.g. from zopectl debug) if os.name != 'posix': return if os.getuid() != 0: return import pwd effective_user = cfg.effective_user if effective_user is None: msg = ('A user was not specified to setuid to; fix this to ' 'start as root (change the effective-user directive ' 'in zope.conf)') logger.critical(msg) raise ZConfig.ConfigurationError(msg) try: uid = int(effective_user) except ValueError: try: pwrec = pwd.getpwnam(effective_user) except KeyError: msg = "Can't find username %r" % effective_user logger.error(msg) raise ZConfig.ConfigurationError(msg) uid = pwrec[2] else: try: pwrec = pwd.getpwuid(uid) except KeyError: msg = "Can't find uid %r" % uid logger.error(msg) raise ZConfig.ConfigurationError(msg) gid = pwrec[3] if uid == 0: msg = 'Cannot start Zope with the effective user as the root user' logger.error(msg) raise ZConfig.ConfigurationError(msg) try: import initgroups initgroups.initgroups(effective_user, gid) os.setgid(gid) except OSError: logger.exception('Could not set group id of effective user') os.setuid(uid) logger.info('Set effective user to "%s"' % effective_user) return 1 # for unit testing purposes # DM 2004-11-24: added def _name2Ips(host, isIp_=compile(r'(\d+\.){3}').match): '''map a name *host* to the sequence of its ip addresses; use *host* itself (as sequence) if it already is an ip address. Thus, if only a specific interface on a host is trusted, identify it by its ip (and not the host name). ''' if isIp_(host): return [host] return gethostbyaddr(host)[2]