index 8dab82ead8c828bbe017bad2b767c9b7328410ce..c14cdf6abf597b70ae1e60422ce2b9333164cac8 100644 (file)
# 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.28 2003-10-05 23:29:49 richard Exp $
+"""Command-line script that runs a server over roundup.cgi.client.
+
+$Id: roundup_server.py,v 1.43 2004-04-05 23:43:04 richard Exp $
"""
+__docformat__ = 'restructuredtext'
# python version check
from roundup import version_check
+from roundup import __version__ as roundup_version
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
#
}
ROUNDUP_USER = None
+ROUNDUP_GROUP = None
+ROUNDUP_LOG_IP = 1
+HOSTNAME = ''
+PORT = 8080
+PIDFILE = None
+LOGFILE = None
#
## end configuration
#
+# "default" favicon.ico
+# generate by using "icotool" and tools/base64
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):
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("<pre>")
- self.wfile.write(cgi.escape(s.getvalue()))
- self.wfile.write("</pre>\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("<pre>")
+ self.wfile.write(cgi.escape(s.getvalue()))
+ self.wfile.write("</pre>\n")
sys.stdin = save_stdin
do_GET = do_POST = run_cgi
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:
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]
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:
'''
_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)
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:
+ -v: print version and exit
-n: sets the host name
- -p: sets the port to listen on
- -l: sets a filename to log to (instead of stdout)
- -d: sets a filename to write server PID to. This option causes the server
- to run in the background. Note: on Windows the PID argument is needed,
- but ignored. The -l option *must* be specified if this option is.
+ -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. 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
pidfile = open(pidfile, 'w')
pidfile.write(str(pid))
pidfile.close()
- os._exit(0)
+ os._exit(0)
- os.chdir("/")
+ os.chdir("/")
os.umask(0)
# close off sys.std(in|out|err), redirect to devnull so the file
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
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:hNv'
+ 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 == '-v':
+ print '%s (python %s)'%(roundup_version, sys.version.split()[0])
+ return
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
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: