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