~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to lib/common/interpret.py

  • Committer: wagrant
  • Date: 2008-09-20 07:02:28 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1053
www.apps.server: Bail out early if the user doesn't exist. This
                 has frequently been crashing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# Date: 18/1/2008
21
21
 
22
22
# Runs a student script in a safe execution environment.
23
 
#
24
 
# NOTE: This script currently disables cookies. This means students will be
25
 
# unable to write session-based or stateful web applications. This is done for
26
 
# security reasons (we do not want the students to see the IVLE cookie of
27
 
# whoever is visiting their pages).
28
 
# This can be resolved but needs careful sanitisation. See fixup_environ.
29
23
 
30
24
from common import studpath
 
25
from common import db
 
26
from common.util import IVLEError, IVLEJailError
31
27
import conf
32
28
import functools
33
29
 
42
38
 
43
39
CGI_BLOCK_SIZE = 65535
44
40
 
45
 
def interpret_file(req, owner, filename, interpreter):
 
41
uids = {}
 
42
 
 
43
def get_uid(login):
 
44
    """Get the unix uid corresponding to the given login name.
 
45
       If it is not in the dictionary of uids, then consult the
 
46
       database and retrieve an update of the user table."""
 
47
    global uids
 
48
    if login in uids:
 
49
        return uids[login]
 
50
 
 
51
    conn = db.DB()
 
52
    res = conn.get_all('login', ['login', 'unixid'])
 
53
    def repack(flds):
 
54
        return (flds['login'], flds['unixid'])
 
55
    uids = dict(map(repack,res))
 
56
 
 
57
    return uids[login]
 
58
 
 
59
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
46
60
    """Serves a file by interpreting it using one of IVLE's builtin
47
61
    interpreters. All interpreters are intended to run in the user's jail. The
48
62
    jail location is provided as an argument to the interpreter but it is up
50
64
 
51
65
    req: An IVLE request object.
52
66
    owner: Username of the user who owns the file being served.
53
 
    filename: Filename in the local file system.
 
67
    jail_dir: Absolute path to the user's jail.
 
68
    filename: Absolute filename within the user's jail.
54
69
    interpreter: A function object to call.
55
70
    """
56
 
    # Make sure the file exists (otherwise some interpreters may not actually
57
 
    # complain).
58
 
    # Don't test for execute permission, that will only be required for
59
 
    # certain interpreters.
60
 
    if not os.access(filename, os.R_OK):
61
 
        req.throw_error(req.HTTP_NOT_FOUND)
 
71
    # We can't test here whether or not the target file actually exists,
 
72
    # because the apache user may not have permission. Instead we have to
 
73
    # rely on the interpreter generating an error.
 
74
    if filename.startswith(os.sep):
 
75
        filename_abs = filename
 
76
        filename_rel = filename[1:]
 
77
    else:
 
78
        filename_abs = os.path.join(os.sep, filename)
 
79
        filename_rel = filename
62
80
 
63
81
    # Get the UID of the owner of the file
64
82
    # (Note: files are executed by their owners, not the logged in user.
65
83
    # This ensures users are responsible for their own programs and also
66
84
    # allows them to be executed by the public).
67
 
    try:
68
 
        (_,_,uid,_,_,_,_) = pwd.getpwnam(owner)
69
 
    except KeyError:
70
 
        # The user does not exist. This should have already failed the
71
 
        # previous test.
72
 
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR)
 
85
    uid = get_uid(owner)
73
86
 
74
87
    # Split up req.path again, this time with respect to the jail
75
 
    (_, jail_dir, path) = studpath.url_to_jailpaths(req.path)
76
 
    path = os.path.join('/', path)
77
 
    (working_dir, _) = os.path.split(path)
 
88
    (working_dir, _) = os.path.split(filename_abs)
78
89
    # jail_dir is the absolute jail directory.
79
90
    # path is the filename relative to the user's jail.
80
91
    # working_dir is the directory containing the file relative to the user's
82
93
    # (Note that paths "relative" to the jail actually begin with a '/' as
83
94
    # they are absolute in the jailspace)
84
95
 
85
 
    return interpreter(uid, jail_dir, working_dir, path, req)
 
96
    return interpreter(uid, jail_dir, working_dir, filename_abs, req,
 
97
                       gentle)
86
98
 
87
99
class CGIFlags:
88
 
    """Stores flags regarding the state of reading CGI output."""
89
 
    def __init__(self):
 
100
    """Stores flags regarding the state of reading CGI output.
 
101
       If this is to be gentle, detection of invalid headers will result in an
 
102
       HTML warning."""
 
103
    def __init__(self, begentle=True):
 
104
        self.gentle = begentle
90
105
        self.started_cgi_body = False
91
106
        self.got_cgi_headers = False
92
107
        self.wrote_html_warning = False
94
109
        self.headers = {}       # Header names : values
95
110
 
96
111
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
97
 
                script_path, req):
 
112
                script_path, req, gentle):
98
113
    """
99
114
    trampoline: Full path on the local system to the CGI wrapper program
100
115
        being executed.
110
125
    its environment.
111
126
    """
112
127
 
 
128
    # Support no-op trampoline runs.
 
129
    if interpreter is None:
 
130
        interpreter = '/bin/true'
 
131
        script_path = ''
 
132
        noop = True
 
133
    else:
 
134
        noop = False
 
135
 
113
136
    # Get the student program's directory and execute it from that context.
114
137
    (tramp_dir, _) = os.path.split(trampoline)
115
138
 
117
140
    # Write the HTTP body to a temporary file so it can be passed as a *real*
118
141
    # file to popen.
119
142
    f = os.tmpfile()
120
 
    body = req.read()
 
143
    body = req.read() if not noop else None
121
144
    if body is not None:
122
145
        f.write(body)
123
146
        f.flush()
146
169
    for (k,v) in old_env.items():
147
170
        os.environ[k] = v
148
171
 
 
172
    # We don't want any output! Bail out after the process terminates.
 
173
    if noop:
 
174
        pid.communicate()
 
175
        return
 
176
 
149
177
    # process_cgi_line: Reads a single line of CGI output and processes it.
150
178
    # Prints to req, and also does fancy HTML warnings if Content-Type
151
179
    # omitted.
152
 
    cgiflags = CGIFlags()
 
180
    cgiflags = CGIFlags(gentle)
153
181
 
154
182
    # Read from the process's stdout into req
155
183
    data = pid.stdout.read(CGI_BLOCK_SIZE)
181
209
        # Break data into lines of CGI header data. 
182
210
        linebuf = cgiflags.linebuf + data
183
211
        # First see if we can split all header data
184
 
        split = linebuf.split('\r\n\r\n', 1)
185
 
        if len(split) == 1:
186
 
            # Allow UNIX newlines instead
187
 
            split = linebuf.split('\n\n', 1)
 
212
        # We need to get the double CRLF- or LF-terminated headers, whichever
 
213
        # is smaller, as either sequence may appear somewhere in the body.
 
214
        usplit = linebuf.split('\n\n', 1)
 
215
        wsplit = linebuf.split('\r\n\r\n', 1)
 
216
        split = len(usplit[0]) > len(wsplit[0]) and wsplit or usplit
188
217
        if len(split) == 1:
189
218
            # Haven't seen all headers yet. Buffer and come back later.
190
219
            cgiflags.linebuf = linebuf
210
239
            if len(split) == 1:
211
240
                split = headers.split('\n', 1)
212
241
 
 
242
        # Is this an internal IVLE error condition?
 
243
        hs = cgiflags.headers
 
244
        if 'X-IVLE-Error-Type' in hs:
 
245
            t = hs['X-IVLE-Error-Type']
 
246
            if t == IVLEError.__name__:
 
247
                raise IVLEError(int(hs['X-IVLE-Error-Code']),
 
248
                                hs['X-IVLE-Error-Message'])
 
249
            else:
 
250
                try:
 
251
                    raise IVLEJailError(hs['X-IVLE-Error-Type'],
 
252
                                        hs['X-IVLE-Error-Message'],
 
253
                                        hs['X-IVLE-Error-Info'])
 
254
                except KeyError:
 
255
                    raise IVLEError(500, 'bad error headers written by CGI')
 
256
 
213
257
        # Check to make sure the required headers were written
214
 
        if cgiflags.wrote_html_warning:
 
258
        if cgiflags.wrote_html_warning or not cgiflags.gentle:
215
259
            # We already reported an error, that's enough
216
260
            pass
217
261
        elif "Content-Type" in cgiflags.headers:
245
289
        name, value = line.split(':', 1)
246
290
    except ValueError:
247
291
        # No colon. The user did not write valid headers.
248
 
        if len(cgiflags.headers) == 0:
 
292
        # If we are being gentle, we want to help the user understand what
 
293
        # went wrong. Otherwise, just admit we screwed up.
 
294
        warning = "Warning"
 
295
        if not cgiflags.gentle:
 
296
            message = """An unexpected server error has occured."""
 
297
            warning = "Error"
 
298
        elif len(cgiflags.headers) == 0:
249
299
            # First line was not a header line. We can assume this is not
250
300
            # a CGI app.
251
301
            message = """You did not print a CGI header.
256
306
            # header.
257
307
            message = """You printed an invalid CGI header. You need to leave
258
308
a blank line after the headers, before writing the page contents."""
259
 
        write_html_warning(req, message)
 
309
        write_html_warning(req, message, warning=warning)
260
310
        cgiflags.wrote_html_warning = True
261
311
        # Handle the rest of this line as normal data
262
312
        process_cgi_output(req, line + '\n', cgiflags)
275
325
        try:
276
326
            req.status = int(value.split(' ', 1)[0])
277
327
        except ValueError:
 
328
            if not cgiflags.gentle:
 
329
                # This isn't user code, so it should be good.
 
330
                # Get us out of here!
 
331
                raise
278
332
            message = """The "Status" CGI header was invalid. You need to
279
333
print a number followed by a message, such as "302 Found"."""
280
334
            write_html_warning(req, message)
284
338
    else:
285
339
        # Generic HTTP header
286
340
        # FIXME: Security risk letting users write arbitrary headers?
287
 
        req.headers_out[name] = value
288
 
    cgiflags.headers[name] = value
 
341
        req.headers_out.add(name, value)
 
342
    cgiflags.headers[name] = value # FIXME: Only the last header will end up here.
289
343
 
290
 
def write_html_warning(req, text):
 
344
def write_html_warning(req, text, warning="Warning"):
291
345
    """Prints an HTML warning about invalid CGI interaction on the part of the
292
346
    user. text may contain HTML markup."""
293
347
    req.content_type = "text/html"
301
355
<body style="margin: 0; padding: 0; font-family: sans-serif;">
302
356
  <div style="background-color: #faa; border-bottom: 1px solid black;
303
357
    padding: 8px;">
304
 
    <p><strong>Warning</strong>: %s
 
358
    <p><strong>%s</strong>: %s
305
359
  </div>
306
360
  <div style="margin: 8px;">
307
361
    <pre>
308
 
""" % text)
 
362
""" % (warning, text))
309
363
 
310
364
location_cgi_python = os.path.join(conf.ivle_install_dir,
311
365
    "bin/trampoline")
317
371
    'cgi-python'
318
372
        : functools.partial(execute_cgi, "/usr/bin/python",
319
373
            location_cgi_python),
 
374
    'noop'
 
375
        : functools.partial(execute_cgi, None,
 
376
            location_cgi_python),
320
377
    # Should also have:
321
378
    # cgi-generic
322
379
    # python-server-page
332
389
    # Comments here are on the heavy side, explained carefully for security
333
390
    # reasons. Please read carefully before making changes.
334
391
 
335
 
    # Remove HTTP_COOKIE. It is a security risk to have students see the IVLE
336
 
    # cookie of their visitors.
337
 
    try:
338
 
        del env['HTTP_COOKIE']
339
 
    except: pass
340
 
 
341
392
    # Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
342
393
    # exposes unnecessary details about server.
343
394
    try: