Code

sssh
[roundup.git] / roundup-server
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()
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
226 # Revision 1.1  2001/07/22 11:15:45  richard
227 # More Grande Splite stuff