Code

disabled the reloading until it can be done properly
[roundup.git] / roundup-server
1 #!/usr/bin/python
2 """ HTTP Server that serves roundup.
4 Stolen from CGIHTTPServer
6 $Id: roundup-server,v 1.4 2001-07-23 10:31:45 richard Exp $
8 """
9 import sys
10 if int(sys.version[0]) < 2:
11     print "Content-Type: text/plain\n"
12     print "Roundup requires Python 2.0 or newer."
13     sys.exit(0)
15 __version__ = "0.1"
17 __all__ = ["RoundupRequestHandler"]
19 import os, urllib, StringIO, traceback, cgi, binascii, string
20 import BaseHTTPServer
21 import SimpleHTTPServer
23 # Roundup modules of use here
24 from roundup import cgitb, cgi_client
26 #
27 ##  Configuration
28 #
30 # This indicates where the Roundup instance lives
31 ROUNDUP_INSTANCE_HOMES = {
32     'bar': '/tmp/bar',
33 }
35 # Where to log debugging information to. Use an instance of DevNull if you
36 # don't want to log anywhere.
37 # TODO: actually use this stuff
38 #class DevNull:
39 #    def write(self, info):
40 #        pass
41 #LOG = open('/var/log/roundup.cgi.log', 'a')
42 #LOG = DevNull()
44 #
45 ##  end configuration
46 #
49 class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
50     def send_head(self):
51         """Version of send_head that support CGI scripts"""
52         # TODO: actually do the HEAD ...
53         return self.run_cgi()
55     def run_cgi(self):
56         """ Execute the CGI command. Wrap an innner call in an error
57             handler so all errors can be caught.
58         """
59         save_stdin = sys.stdin
60         sys.stdin = self.rfile
61         try:
62             self.inner_run_cgi()
63         except cgi_client.Unauthorised:
64             self.wfile.write('Content-Type: text/html\n')
65             self.wfile.write('Status: 403\n')
66             self.wfile.write('Unauthorised')
67         except:
68             try:
69                 reload(cgitb)
70                 self.wfile.write("Content-Type: text/html\n\n")
71                 self.wfile.write(cgitb.breaker())
72                 self.wfile.write(cgitb.html())
73             except:
74                 self.wfile.write("Content-Type: text/html\n\n")
75                 self.wfile.write("<pre>")
76                 s = StringIO.StringIO()
77                 traceback.print_exc(None, s)
78                 self.wfile.write(cgi.escape(s.getvalue()))
79                 self.wfile.write("</pre>\n")
80         sys.stdin = save_stdin
82     def inner_run_cgi(self):
83         ''' This is the inner part of the CGI handling
84         '''
86         rest = self.path
87         i = rest.rfind('?')
88         if i >= 0:
89             rest, query = rest[:i], rest[i+1:]
90         else:
91             query = ''
93         # figure the instance
94         if rest == '/':
95             raise ValueError, 'No instance specified'
96         l_path = string.split(rest, '/')
97         instance = urllib.unquote(l_path[1])
98         if ROUNDUP_INSTANCE_HOMES.has_key(instance):
99             instance_home = ROUNDUP_INSTANCE_HOMES[instance]
100             module_path, instance = os.path.split(instance_home)
101             sys.path.insert(0, module_path)
102             try:
103                 instance = __import__(instance)
104             finally:
105                 del sys.path[0]
106         else:
107             raise ValueError, 'No such instance "%s"'%instance
109         # figure out what the rest of the path is
110         if len(l_path) > 2:
111             rest = '/'.join(l_path[2:])
112         else:
113             rest = '/'
115         # Set up the CGI environment
116         env = {}
117         env['REQUEST_METHOD'] = self.command
118         env['PATH_INFO'] = urllib.unquote(rest)
119         if query:
120             env['QUERY_STRING'] = query
121         host = self.address_string()
122         if self.headers.typeheader is None:
123             env['CONTENT_TYPE'] = self.headers.type
124         else:
125             env['CONTENT_TYPE'] = self.headers.typeheader
126         length = self.headers.getheader('content-length')
127         if length:
128             env['CONTENT_LENGTH'] = length
129         co = filter(None, self.headers.getheaders('cookie'))
130         if co:
131             env['HTTP_COOKIE'] = ', '.join(co)
132         env['SCRIPT_NAME'] = ''
133         env['SERVER_NAME'] = self.server.server_name
134         env['SERVER_PORT'] = str(self.server.server_port)
136         decoded_query = query.replace('+', ' ')
138         # if root, setuid to nobody
139         # TODO why isn't this done much earlier? - say, in main()?
140         if not os.getuid():
141             nobody = nobody_uid()
142             os.setuid(nobody)
144         # reload all modules
145         # TODO check for file timestamp changes and dependencies
146         #reload(date)
147         #reload(hyperdb)
148         #reload(roundupdb)
149         #reload(htmltemplate)
150         #reload(cgi_client)
151         #sys.path.insert(0, module_path)
152         #try:
153         #    reload(instance)
154         #finally:
155         #    del sys.path[0]
157         # initialise the roundupdb, check for auth
158         db = instance.open('admin')
159         message = 'Unauthorised'
160         auth = self.headers.getheader('authorization')
161         if auth:
162             l = binascii.a2b_base64(auth.split(' ')[1]).split(':')
163             user = l[0]
164             password = None
165             if len(l) > 1:
166                 password = l[1]
167             try:
168                 uid = db.user.lookup(user)
169             except KeyError:
170                 auth = None
171                 message = 'Username not recognised'
172             else:
173                 if password != db.user.get(uid, 'password'):
174                     message = 'Incorrect password'
175                     auth = None
176         db.close()
177         del db
178         if not auth:
179             self.send_response(401)
180             self.send_header('Content-Type', 'text/html')
181             self.send_header('WWW-Authenticate', 'basic realm="Roundup"')
182             self.end_headers()
183             self.wfile.write(message)
184             return
186         self.send_response(200, "Script output follows")
188         # do the roundup thang
189         db = instance.open(user)
190         client = instance.Client(self.wfile, db, env, user)
191         client.main()
192     do_POST = run_cgi
194 nobody = None
196 def nobody_uid():
197     """Internal routine to get nobody's uid"""
198     global nobody
199     if nobody:
200         return nobody
201     try:
202         import pwd
203     except ImportError:
204         return -1
205     try:
206         nobody = pwd.getpwnam('nobody')[2]
207     except KeyError:
208         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
209     return nobody
211 if __name__ == '__main__':
212     # TODO make this configurable again? command-line seems ok to me...
213     address = ('', 8080)
214     httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
215     print 'Roundup server started on', address
216     httpd.serve_forever()
219 # $Log: not supported by cvs2svn $
220 # Revision 1.3  2001/07/23 08:53:44  richard
221 # Fixed the ROUNDUPS decl in roundup-server
222 # Move the installation notes to INSTALL
224 # Revision 1.2  2001/07/23 04:05:05  anthonybaxter
225 # actually quit if python version wrong
227 # Revision 1.1  2001/07/23 03:46:48  richard
228 # moving the bin files to facilitate out-of-the-boxness
230 # Revision 1.1  2001/07/22 11:15:45  richard
231 # More Grande Splite stuff