Code

fixed the order of the blank line and '-------' line
[roundup.git] / roundup-server
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
4 # This module is free software, and you may redistribute it and/or modify
5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form.
7 #
8 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
9 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
10 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
11 # POSSIBILITY OF SUCH DAMAGE.
12 #
13 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18
19 """ HTTP Server that serves roundup.
21 Based on CGIHTTPServer in the Python library.
23 $Id: roundup-server,v 1.21 2001-12-02 05:06:16 richard Exp $
25 """
26 import sys
27 if not hasattr(sys, 'version_info') or sys.version_info[:2] < (2,1):
28     print "Roundup requires Python 2.1 or newer."
29     sys.exit(0)
31 import os, urllib, StringIO, traceback, cgi, binascii, string, getopt, imp
32 import BaseHTTPServer
34 # Roundup modules of use here
35 from roundup import cgitb, cgi_client
36 import roundup.instance
38 #
39 ##  Configuration
40 #
42 # This indicates where the Roundup instance lives
43 ROUNDUP_INSTANCE_HOMES = {
44     'bar': '/tmp/bar',
45 }
47 ROUNDUP_USER = None
50 # Where to log debugging information to. Use an instance of DevNull if you
51 # don't want to log anywhere.
52 # TODO: actually use this stuff
53 #class DevNull:
54 #    def write(self, info):
55 #        pass
56 #LOG = open('/var/log/roundup.cgi.log', 'a')
57 #LOG = DevNull()
59 #
60 ##  end configuration
61 #
64 class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
65     ROUNDUP_INSTANCE_HOMES = ROUNDUP_INSTANCE_HOMES
66     ROUNDUP_USER = ROUNDUP_USER
68     def run_cgi(self):
69         """ Execute the CGI command. Wrap an innner call in an error
70             handler so all errors can be caught.
71         """
72         save_stdin = sys.stdin
73         sys.stdin = self.rfile
74         try:
75             self.inner_run_cgi()
76         except cgi_client.NotFound:
77             self.send_error(404, self.path)
78         except cgi_client.Unauthorised:
79             self.send_error(403, self.path)
80         except:
81             # it'd be nice to be able to detect if these are going to have
82             # any effect...
83             self.send_response(400)
84             self.send_header('Content-Type', 'text/html')
85             self.end_headers()
86             try:
87                 reload(cgitb)
88                 self.wfile.write(cgitb.breaker())
89                 self.wfile.write(cgitb.html())
90             except:
91                 self.wfile.write("<pre>")
92                 s = StringIO.StringIO()
93                 traceback.print_exc(None, s)
94                 self.wfile.write(cgi.escape(s.getvalue()))
95                 self.wfile.write("</pre>\n")
96         sys.stdin = save_stdin
98     do_GET = do_POST = do_HEAD = send_head = run_cgi
100     def index(self):
101         ''' Print up an index of the available instances
102         '''
103         self.send_response(200)
104         self.send_header('Content-Type', 'text/html')
105         self.end_headers()
106         w = self.wfile.write
107         w('<html><head><title>Roundup instances index</title></head>\n')
108         w('<body><h1>Roundup instances index</h1><ol>\n')
109         for instance in self.ROUNDUP_INSTANCE_HOMES.keys():
110             w('<li><a href="%s/index">%s</a>\n'%(urllib.quote(instance),
111                 cgi.escape(instance)))
112         w('</ol></body></html>')
114     def inner_run_cgi(self):
115         ''' This is the inner part of the CGI handling
116         '''
118         rest = self.path
119         i = rest.rfind('?')
120         if i >= 0:
121             rest, query = rest[:i], rest[i+1:]
122         else:
123             query = ''
125         # figure the instance
126         if rest == '/':
127             return self.index()
128         l_path = string.split(rest, '/')
129         instance_name = urllib.unquote(l_path[1])
130         if self.ROUNDUP_INSTANCE_HOMES.has_key(instance_name):
131             instance_home = self.ROUNDUP_INSTANCE_HOMES[instance_name]
132             instance = roundup.instance.open(instance_home)
133         else:
134             raise cgi_client.NotFound
136         # figure out what the rest of the path is
137         if len(l_path) > 2:
138             rest = '/'.join(l_path[2:])
139         else:
140             rest = '/'
142         # Set up the CGI environment
143         env = {}
144         env['INSTANCE_NAME'] = instance_name
145         env['REQUEST_METHOD'] = self.command
146         env['PATH_INFO'] = urllib.unquote(rest)
147         if query:
148             env['QUERY_STRING'] = query
149         host = self.address_string()
150         if self.headers.typeheader is None:
151             env['CONTENT_TYPE'] = self.headers.type
152         else:
153             env['CONTENT_TYPE'] = self.headers.typeheader
154         length = self.headers.getheader('content-length')
155         if length:
156             env['CONTENT_LENGTH'] = length
157         co = filter(None, self.headers.getheaders('cookie'))
158         if co:
159             env['HTTP_COOKIE'] = ', '.join(co)
160         env['SCRIPT_NAME'] = ''
161         env['SERVER_NAME'] = self.server.server_name
162         env['SERVER_PORT'] = str(self.server.server_port)
164         decoded_query = query.replace('+', ' ')
166         # do the roundup thang
167         client = instance.Client(instance, self, env)
168         client.main()
170 def usage(message=''):
171     if message: message = 'Error: %s\n\n'%message
172     print '''%sUsage:
173 roundup-server [-n hostname] [-p port] [name=instance home]*
175  -n: sets the host name
176  -p: sets the port to listen on
178  name=instance home
179    Sets the instance home(s) to use. The name is how the instance is
180    identified in the URL (it's the first part of the URL path). The
181    instance home is the directory that was identified when you did
182    "roundup-admin init". You may specify any number of these name=home
183    pairs on the command-line. For convenience, you may edit the
184    ROUNDUP_INSTANCE_HOMES variable in the roundup-server file instead.
185 '''%message
186     sys.exit(0)
188 def main():
189     hostname = ''
190     port = 8080
191     try:
192         # handle the command-line args
193         try:
194             optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:')
195         except getopt.GetoptError, e:
196             usage(str(e))
198         user = ROUNDUP_USER
199         for (opt, arg) in optlist:
200             if opt == '-n': hostname = arg
201             elif opt == '-p': port = int(arg)
202             elif opt == '-u': user = arg
203             elif opt == '-h': usage()
205         if hasattr(os, 'getuid'):
206             # if root, setuid to the running user
207             if not os.getuid() and user is not None:
208                 try:
209                     import pwd
210                 except ImportError:
211                     raise ValueError, "Can't change users - no pwd module"
212                 try:
213                     uid = pwd.getpwnam(user)[2]
214                 except KeyError:
215                     raise ValueError, "User %s doesn't exist"%user
216                 os.setuid(uid)
217             elif os.getuid() and user is not None:
218                 print 'WARNING: ignoring "-u" argument, not root'
220             # People can remove this check if they're really determined
221             if not os.getuid() and user is None:
222                 raise ValueError, "Can't run as root!"
224         # handle instance specs
225         if args:
226             d = {}
227             for arg in args:
228                 try:
229                     name, home = string.split(arg, '=')
230                 except ValueError:
231                     raise ValueError, "Instances must be name=home"
232                 d[name] = home
233             RoundupRequestHandler.ROUNDUP_INSTANCE_HOMES = d
234     except SystemExit:
235         raise
236     except:
237         exc_type, exc_value = sys.exc_info()[:2]
238         usage('%s: %s'%(exc_type, exc_value))
240     # we don't want the cgi module interpreting the command-line args ;)
241     sys.argv = sys.argv[:1]
242     address = (hostname, port)
243     httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
244     print 'Roundup server started on', address
245     httpd.serve_forever()
247 if __name__ == '__main__':
248     main()
251 # $Log: not supported by cvs2svn $
252 # Revision 1.20  2001/11/26 22:55:56  richard
253 # Feature:
254 #  . Added INSTANCE_NAME to configuration - used in web and email to identify
255 #    the instance.
256 #  . Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup
257 #    signature info in e-mails.
258 #  . Some more flexibility in the mail gateway and more error handling.
259 #  . Login now takes you to the page you back to the were denied access to.
261 # Fixed:
262 #  . Lots of bugs, thanks Roché and others on the devel mailing list!
264 # Revision 1.19  2001/11/12 22:51:04  jhermann
265 # Fixed option & associated error handling
267 # Revision 1.18  2001/11/01 22:04:37  richard
268 # Started work on supporting a pop3-fetching server
269 # Fixed bugs:
270 #  . bug #477104 ] HTML tag error in roundup-server
271 #  . bug #477107 ] HTTP header problem
273 # Revision 1.17  2001/10/29 23:55:44  richard
274 # Fix to CGI top-level index (thanks Juergen Hermann)
276 # Revision 1.16  2001/10/27 00:12:21  richard
277 # Fixed roundup-server for windows, thanks Juergen Hermann.
279 # Revision 1.15  2001/10/12 02:23:26  richard
280 # Didn't clean up after myself :)
282 # Revision 1.14  2001/10/12 02:20:32  richard
283 # server now handles setuid'ing much better
285 # Revision 1.13  2001/10/05 02:23:24  richard
286 #  . roundup-admin create now prompts for property info if none is supplied
287 #    on the command-line.
288 #  . hyperdb Class getprops() method may now return only the mutable
289 #    properties.
290 #  . Login now uses cookies, which makes it a whole lot more flexible. We can
291 #    now support anonymous user access (read-only, unless there's an
292 #    "anonymous" user, in which case write access is permitted). Login
293 #    handling has been moved into cgi_client.Client.main()
294 #  . The "extended" schema is now the default in roundup init.
295 #  . The schemas have had their page headings modified to cope with the new
296 #    login handling. Existing installations should copy the interfaces.py
297 #    file from the roundup lib directory to their instance home.
298 #  . Incorrectly had a Bizar Software copyright on the cgitb.py module from
299 #    Ping - has been removed.
300 #  . Fixed a whole bunch of places in the CGI interface where we should have
301 #    been returning Not Found instead of throwing an exception.
302 #  . Fixed a deviation from the spec: trying to modify the 'id' property of
303 #    an item now throws an exception.
305 # Revision 1.12  2001/09/29 13:27:00  richard
306 # CGI interfaces now spit up a top-level index of all the instances they can
307 # serve.
309 # Revision 1.11  2001/08/07 00:24:42  richard
310 # stupid typo
312 # Revision 1.10  2001/08/07 00:15:51  richard
313 # Added the copyright/license notice to (nearly) all files at request of
314 # Bizar Software.
316 # Revision 1.9  2001/08/05 07:44:36  richard
317 # Instances are now opened by a special function that generates a unique
318 # module name for the instances on import time.
320 # Revision 1.8  2001/08/03 01:28:33  richard
321 # Used the much nicer load_package, pointed out by Steve Majewski.
323 # Revision 1.7  2001/08/03 00:59:34  richard
324 # Instance import now imports the instance using imp.load_module so that
325 # we can have instance homes of "roundup" or other existing python package
326 # names.
328 # Revision 1.6  2001/07/29 07:01:39  richard
329 # Added vim command to all source so that we don't get no steenkin' tabs :)
331 # Revision 1.5  2001/07/24 01:07:59  richard
332 # Added command-line arg handling to roundup-server so it's more useful
333 # out-of-the-box.
335 # Revision 1.4  2001/07/23 10:31:45  richard
336 # disabled the reloading until it can be done properly
338 # Revision 1.3  2001/07/23 08:53:44  richard
339 # Fixed the ROUNDUPS decl in roundup-server
340 # Move the installation notes to INSTALL
342 # Revision 1.2  2001/07/23 04:05:05  anthonybaxter
343 # actually quit if python version wrong
345 # Revision 1.1  2001/07/23 03:46:48  richard
346 # moving the bin files to facilitate out-of-the-boxness
348 # Revision 1.1  2001/07/22 11:15:45  richard
349 # More Grande Splite stuff
352 # vim: set filetype=python ts=4 sw=4 et si