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()
218 #
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
223 #
224 # Revision 1.2 2001/07/23 04:05:05 anthonybaxter
225 # actually quit if python version wrong
226 #
227 # Revision 1.1 2001/07/23 03:46:48 richard
228 # moving the bin files to facilitate out-of-the-boxness
229 #
230 # Revision 1.1 2001/07/22 11:15:45 richard
231 # More Grande Splite stuff
232 #
233 #