9b96638a97f122714b3a0d240ea24bfccb5d6fbc
1 #!/usr/bin/python
2 """ HTTP Server that serves roundup.
4 Stolen from CGIHTTPServer
6 $Id: roundup-server,v 1.9 2001-08-05 07:44:36 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, getopt, imp
20 import BaseHTTPServer
21 import SimpleHTTPServer
23 # Roundup modules of use here
24 from roundup import cgitb, cgi_client
25 import roundup.instance
27 #
28 ## Configuration
29 #
31 # This indicates where the Roundup instance lives
32 ROUNDUP_INSTANCE_HOMES = {
33 'bar': '/tmp/bar',
34 }
36 # Where to log debugging information to. Use an instance of DevNull if you
37 # don't want to log anywhere.
38 # TODO: actually use this stuff
39 #class DevNull:
40 # def write(self, info):
41 # pass
42 #LOG = open('/var/log/roundup.cgi.log', 'a')
43 #LOG = DevNull()
45 #
46 ## end configuration
47 #
50 class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
51 ROUNDUP_INSTANCE_HOMES = ROUNDUP_INSTANCE_HOMES
52 def send_head(self):
53 """Version of send_head that support CGI scripts"""
54 # TODO: actually do the HEAD ...
55 return self.run_cgi()
57 def run_cgi(self):
58 """ Execute the CGI command. Wrap an innner call in an error
59 handler so all errors can be caught.
60 """
61 save_stdin = sys.stdin
62 sys.stdin = self.rfile
63 try:
64 self.inner_run_cgi()
65 except cgi_client.Unauthorised:
66 self.wfile.write('Content-Type: text/html\n')
67 self.wfile.write('Status: 403\n')
68 self.wfile.write('Unauthorised')
69 except:
70 try:
71 reload(cgitb)
72 self.wfile.write("Content-Type: text/html\n\n")
73 self.wfile.write(cgitb.breaker())
74 self.wfile.write(cgitb.html())
75 except:
76 self.wfile.write("Content-Type: text/html\n\n")
77 self.wfile.write("<pre>")
78 s = StringIO.StringIO()
79 traceback.print_exc(None, s)
80 self.wfile.write(cgi.escape(s.getvalue()))
81 self.wfile.write("</pre>\n")
82 sys.stdin = save_stdin
84 def inner_run_cgi(self):
85 ''' This is the inner part of the CGI handling
86 '''
88 rest = self.path
89 i = rest.rfind('?')
90 if i >= 0:
91 rest, query = rest[:i], rest[i+1:]
92 else:
93 query = ''
95 # figure the instance
96 if rest == '/':
97 raise ValueError, 'No instance specified'
98 l_path = string.split(rest, '/')
99 instance = urllib.unquote(l_path[1])
100 if self.ROUNDUP_INSTANCE_HOMES.has_key(instance):
101 instance_home = self.ROUNDUP_INSTANCE_HOMES[instance]
102 instance = roundup.instance.open(instance_home)
103 else:
104 raise ValueError, 'No such instance "%s"'%instance
106 # figure out what the rest of the path is
107 if len(l_path) > 2:
108 rest = '/'.join(l_path[2:])
109 else:
110 rest = '/'
112 # Set up the CGI environment
113 env = {}
114 env['REQUEST_METHOD'] = self.command
115 env['PATH_INFO'] = urllib.unquote(rest)
116 if query:
117 env['QUERY_STRING'] = query
118 host = self.address_string()
119 if self.headers.typeheader is None:
120 env['CONTENT_TYPE'] = self.headers.type
121 else:
122 env['CONTENT_TYPE'] = self.headers.typeheader
123 length = self.headers.getheader('content-length')
124 if length:
125 env['CONTENT_LENGTH'] = length
126 co = filter(None, self.headers.getheaders('cookie'))
127 if co:
128 env['HTTP_COOKIE'] = ', '.join(co)
129 env['SCRIPT_NAME'] = ''
130 env['SERVER_NAME'] = self.server.server_name
131 env['SERVER_PORT'] = str(self.server.server_port)
133 decoded_query = query.replace('+', ' ')
135 # if root, setuid to nobody
136 # TODO why isn't this done much earlier? - say, in main()?
137 if not os.getuid():
138 nobody = nobody_uid()
139 os.setuid(nobody)
141 # reload all modules
142 # TODO check for file timestamp changes and dependencies
143 #reload(date)
144 #reload(hyperdb)
145 #reload(roundupdb)
146 #reload(htmltemplate)
147 #reload(cgi_client)
148 #sys.path.insert(0, module_path)
149 #try:
150 # reload(instance)
151 #finally:
152 # del sys.path[0]
154 # initialise the roundupdb, check for auth
155 db = instance.open('admin')
156 message = 'Unauthorised'
157 auth = self.headers.getheader('authorization')
158 if auth:
159 l = binascii.a2b_base64(auth.split(' ')[1]).split(':')
160 user = l[0]
161 password = None
162 if len(l) > 1:
163 password = l[1]
164 try:
165 uid = db.user.lookup(user)
166 except KeyError:
167 auth = None
168 message = 'Username not recognised'
169 else:
170 if password != db.user.get(uid, 'password'):
171 message = 'Incorrect password'
172 auth = None
173 db.close()
174 del db
175 if not auth:
176 self.send_response(401)
177 self.send_header('Content-Type', 'text/html')
178 self.send_header('WWW-Authenticate', 'basic realm="Roundup"')
179 self.end_headers()
180 self.wfile.write(message)
181 return
183 self.send_response(200, "Script output follows")
185 # do the roundup thang
186 db = instance.open(user)
187 client = instance.Client(self.wfile, db, env, user)
188 client.main()
189 do_POST = run_cgi
191 nobody = None
193 def nobody_uid():
194 """Internal routine to get nobody's uid"""
195 global nobody
196 if nobody:
197 return nobody
198 try:
199 import pwd
200 except ImportError:
201 return -1
202 try:
203 nobody = pwd.getpwnam('nobody')[2]
204 except KeyError:
205 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
206 return nobody
208 def usage(message=''):
209 if message: message = 'Error: %s\n'%message
210 print '''%sUsage:
211 roundup-server [-n hostname] [-p port] [name=instance home]*
213 -n: sets the host name
214 -p: sets the port to listen on
216 name=instance home
217 Sets the instance home(s) to use. The name is how the instance is
218 identified in the URL (it's the first part of the URL path). The
219 instance home is the directory that was identified when you did
220 "roundup-admin init". You may specify any number of these name=home
221 pairs on the command-line. For convenience, you may edit the
222 ROUNDUP_INSTANCE_HOMES variable in the roundup-server file instead.
223 '''%message
224 sys.exit(0)
226 def main():
227 hostname = ''
228 port = 8080
229 try:
230 # handle the command-line args
231 optlist, args = getopt.getopt(sys.argv[1:], 'n:p:')
232 for (opt, arg) in optlist:
233 if opt == '-n': hostname = arg
234 elif opt == '-p': port = int(arg)
235 elif opt == '-h': usage()
237 # handle instance specs
238 if args:
239 d = {}
240 for arg in args:
241 name, home = string.split(arg, '=')
242 d[name] = home
243 RoundupRequestHandler.ROUNDUP_INSTANCE_HOMES = d
244 except:
245 type, value = sys.exc_info()[:2]
246 usage('%s: %s'%(type, value))
248 # we don't want the cgi module interpreting the command-line args ;)
249 sys.argv = sys.argv[:1]
250 address = (hostname, port)
251 httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
252 print 'Roundup server started on', address
253 httpd.serve_forever()
255 if __name__ == '__main__':
256 main()
258 #
259 # $Log: not supported by cvs2svn $
260 # Revision 1.8 2001/08/03 01:28:33 richard
261 # Used the much nicer load_package, pointed out by Steve Majewski.
262 #
263 # Revision 1.7 2001/08/03 00:59:34 richard
264 # Instance import now imports the instance using imp.load_module so that
265 # we can have instance homes of "roundup" or other existing python package
266 # names.
267 #
268 # Revision 1.6 2001/07/29 07:01:39 richard
269 # Added vim command to all source so that we don't get no steenkin' tabs :)
270 #
271 # Revision 1.5 2001/07/24 01:07:59 richard
272 # Added command-line arg handling to roundup-server so it's more useful
273 # out-of-the-box.
274 #
275 # Revision 1.4 2001/07/23 10:31:45 richard
276 # disabled the reloading until it can be done properly
277 #
278 # Revision 1.3 2001/07/23 08:53:44 richard
279 # Fixed the ROUNDUPS decl in roundup-server
280 # Move the installation notes to INSTALL
281 #
282 # Revision 1.2 2001/07/23 04:05:05 anthonybaxter
283 # actually quit if python version wrong
284 #
285 # Revision 1.1 2001/07/23 03:46:48 richard
286 # moving the bin files to facilitate out-of-the-boxness
287 #
288 # Revision 1.1 2001/07/22 11:15:45 richard
289 # More Grande Splite stuff
290 #
291 #
292 # vim: set filetype=python ts=4 sw=4 et si