X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fscripts%2Froundup_server.py;h=2b20fd7b7d2d24a12c75ba416c7d099cb7935070;hb=279d687a1b55fb3aff972a38a087ab66ed5fdd72;hp=1575f418f94522a2978be811a5675c073b6dc778;hpb=f64e0275b0de4071d8048753258cbbbec47d89f4;p=roundup.git diff --git a/roundup/scripts/roundup_server.py b/roundup/scripts/roundup_server.py index 1575f41..2b20fd7 100644 --- a/roundup/scripts/roundup_server.py +++ b/roundup/scripts/roundup_server.py @@ -14,22 +14,29 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -""" HTTP Server that serves roundup. -$Id: roundup_server.py,v 1.26 2003-08-12 01:14:11 richard Exp $ +"""Command-line script that runs a server over roundup.cgi.client. + +$Id: roundup_server.py,v 1.41 2004-04-05 00:51:45 richard Exp $ """ +__docformat__ = 'restructuredtext' # python version check from roundup import version_check import sys, os, urllib, StringIO, traceback, cgi, binascii, getopt, imp -import BaseHTTPServer, socket +import BaseHTTPServer, socket, errno # Roundup modules of use here from roundup.cgi import cgitb, client import roundup.instance from roundup.i18n import _ +try: + import signal +except: + signal = None + # ## Configuration # @@ -40,35 +47,34 @@ from roundup.i18n import _ # Make sure the NAME part doesn't include any url-unsafe characters like # spaces, as these confuse the cookie handling in browsers like IE. TRACKER_HOMES = { - 'bar': '/tmp/bar', +# 'example': '/path/to/example', } ROUNDUP_USER = None +ROUNDUP_GROUP = None +ROUNDUP_LOG_IP = 1 +HOSTNAME = '' +PORT = 8080 +PIDFILE = None +LOGFILE = None -# Where to log debugging information to. Use an instance of DevNull if you -# don't want to log anywhere. -# TODO: actually use this stuff -#class DevNull: -# def write(self, info): -# pass -#LOG = open('/var/log/roundup.cgi.log', 'a') -#LOG = DevNull() - # ## end configuration # import zlib, base64 favico = zlib.decompress(base64.decodestring(''' -eJyVUk2IQVEUfn4yaRYjibdQZiVba/ZE2djIUmHWFjaKGVmIlY2iFMVG2ViQhXqFSP6iFFJvw4uF -LGdWd743mpeMn+a88917Oue7955z3qEoET6FQkHx8iahKDV2A8B7XgERRf/EKMSUzyf8ypbbnnQy -mWBdr9eVSkVw3tJGoxGNRpvNZigUyufzWPv9Pvwcx0UiERj7/V4g73Y7j8fTarWMRmO73U4kEkKI -YZhardbr9eLxuOD0+/2ZTMZisYjFYpqmU6kU799uN5tNMBg8HA7ZbPY8GaTh8/mEipRKpclk0ul0 -NpvNarUmk0mWZS/yr9frcrmc+iMOh+NWydPp1Ov1SiSSc344HL7fKKfTiSN2u12tVqOcxWJxn6/V -ag0GAwxkrlKp5vP5fT7ulMlk6XRar9dLpVIUXi6Xb5Hxa1wul0ajKZVKsVjM7XYXCoVOp3OVPJvN -AoFAtVo1m825XO7hSODOYrH4kHbxxGAwwODBGI/H6DBs5LNara7yl8slGjIcDsHpdrunU6PRCAP2 -r3fPdUcIYeyEfLSAJ0LeAUZHCAt8Al/8/kLIEWDB5YDj0wm8fAP6fVfo +eJztjr1PmlEUh59XgVoshdYPWorFIhaRFq0t9pNq37b60lYSTRzcTFw6GAfj5gDYaF0dTB0MxMSE +gQQd3FzKJiEC0UCIUUN1M41pV2JCXySg/0ITn5tfzvmdc+85FwT56HSc81UJjXJsk1UsNcsSqCk1 +BS64lK+vr7OyssLJyQl2ux2j0cjU1BQajYZIJEIwGMRms+H3+zEYDExOTjI2Nsbm5iZWqxWv18vW +1hZDQ0Ok02kmJiY4Ojpienqa3d1dxsfHUSqVeDwe5ufnyeVyrK6u4nK5ODs7Y3FxEYfDwdzcHCaT +icPDQ5LJJIIgMDIyQj6fZ39/n+3tbdbW1pAkiYWFBWZmZtjb2yMejzM8PEwgEMDn85HNZonFYqjV +asLhMMvLy2QyGfR6PaOjowwODmKxWDg+PkalUhEKhSgUCiwtLWE2m9nZ2UGhULCxscHp6SmpVIpo +NMrs7CwHBwdotVoSiQRXXPG/IzY7RHtt922xjFRb01H1XhKfPBNbi/7my7rrLXJ88eppvxwEfV3f +NY3Y6exofVdsV3+2wnPFDdPjB83n7xuVpcFvygPbGwxF31LZIKrQDfR2Xvh7lmrX654L/7bvlnng +bn3Zuj8M9Hepux6VfZtW1yA6K7cfGqVu8TL325u+fHTb71QKbk+7TZQ+lTc6RcnpqW8qmVQBoj/g +23eo0sr/NIGvB37K+lOWXMvJ+uWFeKGU/03Cb7n3D4M3wxI= '''.strip())) class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -88,21 +94,27 @@ class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): except client.Unauthorised: self.send_error(403, self.path) except: - # it'd be nice to be able to detect if these are going to have - # any effect... - self.send_response(400) - self.send_header('Content-Type', 'text/html') - self.end_headers() - try: - reload(cgitb) - self.wfile.write(cgitb.breaker()) - self.wfile.write(cgitb.html()) - except: + exc, val, tb = sys.exc_info() + if hasattr(socket, 'timeout') and exc == socket.timeout: s = StringIO.StringIO() traceback.print_exc(None, s) - self.wfile.write("
")
-                self.wfile.write(cgi.escape(s.getvalue()))
-                self.wfile.write("
\n") + self.log_message(str(s.getvalue())) + else: + # it'd be nice to be able to detect if these are going to have + # any effect... + self.send_response(400) + self.send_header('Content-Type', 'text/html') + self.end_headers() + try: + reload(cgitb) + self.wfile.write(cgitb.breaker()) + self.wfile.write(cgitb.html()) + except: + s = StringIO.StringIO() + traceback.print_exc(None, s) + self.wfile.write("
")
+                    self.wfile.write(cgi.escape(s.getvalue()))
+                    self.wfile.write("
\n") sys.stdin = save_stdin do_GET = do_POST = run_cgi @@ -130,12 +142,11 @@ class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): rest = self.path if rest == '/favicon.ico': - raise client.NotFound -# self.send_response(200) -# self.send_header('Content-Type', 'image/x-ico') -# self.end_headers() -# self.wfile.write(favicon) -# return + self.send_response(200) + self.send_header('Content-Type', 'image/x-icon') + self.end_headers() + self.wfile.write(favico) + return i = rest.rfind('?') if i >= 0: @@ -204,7 +215,7 @@ class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): c = tracker.Client(tracker, self, env) c.main() - LOG_IPADDRESS = 1 + LOG_IPADDRESS = ROUNDUP_LOG_IP def address_string(self): if self.LOG_IPADDRESS: return self.client_address[0] @@ -212,6 +223,20 @@ class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): host, port = self.client_address return socket.getfqdn(host) + def log_message(self, format, *args): + ''' Try to *safely* log to stderr. + ''' + try: + BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, + format, *args) + except IOError: + # stderr is no longer viable + pass + +def error(): + exc_type, exc_value = sys.exc_info()[:2] + return _('Error: %s: %s' % (exc_type, exc_value)) + try: import win32serviceutil except: @@ -231,8 +256,12 @@ else: ''' _svc_name_ = "Roundup Bug Tracker" _svc_display_name_ = "Roundup Bug Tracker" - address = ('', 8888) + address = (HOSTNAME, PORT) def __init__(self, args): + # redirect stdout/stderr to our logfile + if LOGFILE: + # appending, unbuffered + sys.stdout = sys.stderr = open(LOGFILE, 'a', 0) win32serviceutil.ServiceFramework.__init__(self, args) BaseHTTPServer.HTTPServer.__init__(self, self.address, RoundupRequestHandler) @@ -303,22 +332,33 @@ else: break return rv - def usage(message=''): - if message: - message = _('Error: %(error)s\n\n')%{'error': message} - print _('''%(message)sUsage: + if RoundupService: + win = ''' -c: Windows Service options. If you want to run the server as a Windows + Service, you must configure the rest of the options by changing the + constants of this program. You will at least configure one tracker + in the TRACKER_HOMES variable. This option is mutually exclusive + from the rest. Typing "roundup-server -c help" shows Windows + Services specifics.''' + else: + win = '' + port=PORT + print _('''%(message)s +Usage: roundup-server [options] [name=tracker home]* options: -n: sets the host name - -p: sets the port to listen on - -l: sets a filename to log to (instead of stdout) + -p: sets the port to listen on (default: %(port)s) + -u: sets the uid to this user after listening on the port + -g: sets the gid to this group after listening on the port + -l: sets a filename to log to (instead of stderr / stdout) -d: run the server in the background and on UN*X write the server's PID - to the nominated file. Note: on Windows the PID argument is needed, - but ignored. The -l option *must* be specified if this option is. + to the nominated file. The -l option *must* be specified if this + option is. -N: log client machine names in access log instead of IP addresses (much slower) +%(win)s name=tracker home: Sets the tracker home(s) to use. The name is how the tracker is @@ -367,16 +407,7 @@ def daemonize(pidfile): os.dup2(devnull, 1) os.dup2(devnull, 2) -def abspath(path): - ''' Make the given path an absolute path. - - Code from Zope-Coders posting of 2002-10-06 by GvR. - ''' - if not os.path.isabs(path): - path = os.path.join(os.getcwd(), path) - return os.path.normpath(path) - -def run(): +def run(port=PORT, success_message=None): ''' Script entry point - handle args and figure out what to to. ''' # time out after a minute if we can @@ -384,29 +415,68 @@ def run(): if hasattr(socket, 'setdefaulttimeout'): socket.setdefaulttimeout(60) - hostname = '' - port = 8080 - pidfile = None - logfile = None + hostname = HOSTNAME + pidfile = PIDFILE + logfile = LOGFILE + user = ROUNDUP_USER + group = ROUNDUP_GROUP + svc_args = None + try: # handle the command-line args + options = 'n:p:u:d:l:hN' + if RoundupService: + options += 'c' + try: - optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:d:l:hN') + optlist, args = getopt.getopt(sys.argv[1:], options) except getopt.GetoptError, e: usage(str(e)) user = ROUNDUP_USER + group = None for (opt, arg) in optlist: if opt == '-n': hostname = arg elif opt == '-p': port = int(arg) elif opt == '-u': user = arg - elif opt == '-d': pidfile = abspath(arg) - elif opt == '-l': logfile = abspath(arg) + elif opt == '-g': group = arg + elif opt == '-d': pidfile = os.path.abspath(arg) + elif opt == '-l': logfile = os.path.abspath(arg) elif opt == '-h': usage() elif opt == '-N': RoundupRequestHandler.LOG_IPADDRESS = 0 + elif opt == '-c': svc_args = [opt] + args; args = None + + if svc_args is not None and len(optlist) > 1: + raise ValueError, _("windows service option must be the only one") if pidfile and not logfile: raise ValueError, _("logfile *must* be specified if pidfile is") + + # obtain server before changing user id - allows to use port < + # 1024 if started as root + address = (hostname, port) + try: + httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler) + except socket.error, e: + if e[0] == errno.EADDRINUSE: + raise socket.error, \ + _("Unable to bind to port %s, port already in use." % port) + raise + + if group is not None and hasattr(os, 'getgid'): + # if root, setgid to the running user + if not os.getgid() and user is not None: + try: + import pwd + except ImportError: + raise ValueError, _("Can't change groups - no pwd module") + try: + gid = pwd.getpwnam(user)[3] + except KeyError: + raise ValueError,_("Group %(group)s doesn't exist")%locals() + os.setgid(gid) + elif os.getgid() and user is not None: + print _('WARNING: ignoring "-g" argument, not root') if hasattr(os, 'getuid'): # if root, setuid to the running user @@ -431,42 +501,45 @@ def run(): if args: d = {} for arg in args: - try: + try: name, home = arg.split('=') except ValueError: raise ValueError, _("Instances must be name=home") - d[name] = home + d[name] = os.path.abspath(home) RoundupRequestHandler.TRACKER_HOMES = d except SystemExit: raise + except ValueError: + usage(error()) except: - exc_type, exc_value = sys.exc_info()[:2] - usage('%s: %s'%(exc_type, exc_value)) + print error() + sys.exit(1) # we don't want the cgi module interpreting the command-line args ;) sys.argv = sys.argv[:1] - address = (hostname, port) - # fork? if pidfile: - if RoundupService: - # don't do any other stuff - RoundupService.address = address - return win32serviceutil.HandleCommandLine(RoundupService) - elif not hasattr(os, 'fork'): + if not hasattr(os, 'fork'): print "Sorry, you can't run the server as a daemon on this" \ 'Operating System' sys.exit(0) else: daemonize(pidfile) + if svc_args is not None: + # don't do any other stuff + return win32serviceutil.HandleCommandLine(RoundupService, argv=svc_args) + # redirect stdout/stderr to our logfile if logfile: # appending, unbuffered sys.stdout = sys.stderr = open(logfile, 'a', 0) - httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler) - print _('Roundup server started on %(address)s')%locals() + if success_message: + print success_message + else: + print _('Roundup server started on %(address)s')%locals() + try: httpd.serve_forever() except KeyboardInterrupt: