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

« back to all changes in this revision

Viewing changes to ivle/interpret.py

  • Committer: Matt Giuca
  • Date: 2010-07-20 05:22:02 UTC
  • Revision ID: matt.giuca@gmail.com-20100720052202-m321ti14t472rrb0
ivle/webapp/submit: Added a 'fullpath' method to SubmitView classes, to get the original URL of the request.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
# Runs a student script in a safe execution environment.
23
23
 
 
24
import ivle
24
25
from ivle import studpath
25
 
from ivle.util import IVLEError, IVLEJailError, split_path
26
 
import ivle.conf
 
26
from ivle.util import IVLEJailError, split_path
27
27
 
28
28
import functools
29
29
 
37
37
# working on smaller output
38
38
 
39
39
CGI_BLOCK_SIZE = 65535
 
40
PATH = "/usr/local/bin:/usr/bin:/bin"
40
41
 
41
 
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
 
42
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True,
 
43
    overrides=None):
42
44
    """Serves a file by interpreting it using one of IVLE's builtin
43
45
    interpreters. All interpreters are intended to run in the user's jail. The
44
46
    jail location is provided as an argument to the interpreter but it is up
49
51
    jail_dir: Absolute path to the user's jail.
50
52
    filename: Absolute filename within the user's jail.
51
53
    interpreter: A function object to call.
 
54
    gentle: ?
 
55
    overrides: A dict mapping env var names to strings, to override arbitrary
 
56
        environment variables in the resulting CGI environent.
52
57
    """
53
58
    # We can't test here whether or not the target file actually exists,
54
59
    # because the apache user may not have permission. Instead we have to
73
78
    # (Note that paths "relative" to the jail actually begin with a '/' as
74
79
    # they are absolute in the jailspace)
75
80
 
76
 
    return interpreter(owner.unixid, jail_dir, working_dir, filename_abs, req,
77
 
                       gentle)
 
81
    return interpreter(owner, jail_dir, working_dir, filename_abs, req,
 
82
                       gentle, overrides=overrides)
78
83
 
79
84
class CGIFlags:
80
85
    """Stores flags regarding the state of reading CGI output.
88
93
        self.linebuf = ""
89
94
        self.headers = {}       # Header names : values
90
95
 
91
 
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
92
 
                script_path, req, gentle):
 
96
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
 
97
                req, gentle, overrides=None):
93
98
    """
94
99
    trampoline: Full path on the local system to the CGI wrapper program
95
100
        being executed.
96
 
    uid: User ID of the owner of the file.
 
101
    owner: User object of the owner of the file.
97
102
    jail_dir: Absolute path of owner's jail directory.
98
103
    working_dir: Directory containing the script file relative to owner's
99
104
        jail.
100
105
    script_path: CGI script relative to the owner's jail.
101
106
    req: IVLE request object.
 
107
    gentle: ?
 
108
    overrides: A dict mapping env var names to strings, to override arbitrary
 
109
        environment variables in the resulting CGI environent.
102
110
 
103
111
    The called CGI wrapper application shall be called using popen and receive
104
112
    the HTTP body on stdin. It shall receive the CGI environment variables to
105
113
    its environment.
106
114
    """
107
115
 
 
116
    trampoline = os.path.join(req.config['paths']['lib'], 'trampoline')
 
117
 
108
118
    # Support no-op trampoline runs.
109
119
    if interpreter is None:
110
120
        interpreter = '/bin/true'
127
137
        f.seek(0)       # Rewind, for reading
128
138
 
129
139
    # Set up the environment
130
 
    # This automatically asks mod_python to load up the CGI variables into the
131
 
    # environment (which is a good first approximation)
132
 
    old_env = os.environ.copy()
133
 
    for k in os.environ.keys():
134
 
        del os.environ[k]
135
 
    for (k,v) in req.get_cgi_environ().items():
136
 
        os.environ[k] = v
137
 
    fixup_environ(req, script_path)
 
140
    environ = cgi_environ(req, script_path, owner, overrides=overrides)
138
141
 
139
142
    # usage: tramp uid jail_dir working_dir script_path
140
 
    pid = subprocess.Popen(
141
 
        [trampoline, str(uid), ivle.conf.jail_base, ivle.conf.jail_src_base,
142
 
         ivle.conf.jail_system, jail_dir, working_dir, interpreter,
143
 
        script_path],
 
143
    cmd_line = [trampoline, str(owner.unixid),
 
144
            req.config['paths']['jails']['mounts'],
 
145
            req.config['paths']['jails']['src'],
 
146
            req.config['paths']['jails']['template'],
 
147
            jail_dir, working_dir, interpreter, script_path]
 
148
    # Popen doesn't like unicode strings. It hateses them.
 
149
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
 
150
                for s in cmd_line]
 
151
    pid = subprocess.Popen(cmd_line,
144
152
        stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
145
 
        cwd=tramp_dir)
146
 
 
147
 
    # Restore the environment
148
 
    for k in os.environ.keys():
149
 
        del os.environ[k]
150
 
    for (k,v) in old_env.items():
151
 
        os.environ[k] = v
 
153
        cwd=tramp_dir, env=environ)
152
154
 
153
155
    # We don't want any output! Bail out after the process terminates.
154
156
    if noop:
220
222
            if len(split) == 1:
221
223
                split = headers.split('\n', 1)
222
224
 
223
 
        # Is this an internal IVLE error condition?
224
 
        hs = cgiflags.headers
225
 
        if 'X-IVLE-Error-Type' in hs:
226
 
            t = hs['X-IVLE-Error-Type']
227
 
            if t == IVLEError.__name__:
228
 
                raise IVLEError(int(hs['X-IVLE-Error-Code']),
229
 
                                hs['X-IVLE-Error-Message'])
230
 
            else:
 
225
        # If not executing in gentle mode (which presents CGI violations
 
226
        # to users nicely), check if this an internal IVLE error
 
227
        # condition.
 
228
        if not cgiflags.gentle:
 
229
            hs = cgiflags.headers
 
230
            if 'X-IVLE-Error-Type' in hs:
231
231
                try:
232
232
                    raise IVLEJailError(hs['X-IVLE-Error-Type'],
233
233
                                        hs['X-IVLE-Error-Message'],
234
234
                                        hs['X-IVLE-Error-Info'])
235
235
                except KeyError:
236
 
                    raise IVLEError(500, 'bad error headers written by CGI')
 
236
                    raise AssertionError("Bad error headers written by CGI.")
237
237
 
238
238
        # Check to make sure the required headers were written
239
239
        if cgiflags.wrote_html_warning or not cgiflags.gentle:
293
293
        process_cgi_output(req, line + '\n', cgiflags)
294
294
        return
295
295
 
 
296
    # Check if CGI field-name is valid
 
297
    CGI_SEPERATORS = set(['(', ')', '<', '>', '@', ',', ';', ':', '\\', '"',
 
298
            '/', '[', ']', '?', '=', '{', '}', ' ', '\t'])
 
299
    if any((char in CGI_SEPERATORS for char in name)):
 
300
        warning = "Warning"
 
301
        if not cgiflags.gentle:
 
302
            message = """An unexpected server error has occured."""
 
303
            warning = "Error"
 
304
        else:
 
305
            # Header contained illegal characters
 
306
            message = """You printed an invalid CGI header. CGI header
 
307
            field-names can not contain any of the following characters: 
 
308
            <code>( ) &lt; &gt; @ , ; : \\ " / [ ] ? = { } <em>SPACE 
 
309
            TAB</em></code>."""
 
310
        write_html_warning(req, message, warning=warning)
 
311
        cgiflags.wrote_html_warning = True
 
312
        # Handle the rest of this line as normal data
 
313
        process_cgi_output(req, line + '\n', cgiflags)
 
314
        return
 
315
 
296
316
    # Read CGI headers
297
317
    value = value.strip()
298
318
    if name == "Content-Type":
342
362
    <pre>
343
363
""" % (warning, text))
344
364
 
345
 
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
346
 
 
347
365
# Mapping of interpreter names (as given in conf/app/server.py) to
348
366
# interpreter functions.
349
367
 
350
368
interpreter_objects = {
351
369
    'cgi-python'
352
 
        : functools.partial(execute_cgi, "/usr/bin/python",
353
 
            location_cgi_python),
 
370
        : functools.partial(execute_cgi, "/usr/bin/python"),
354
371
    'noop'
355
 
        : functools.partial(execute_cgi, None,
356
 
            location_cgi_python),
 
372
        : functools.partial(execute_cgi, None),
357
373
    # Should also have:
358
374
    # cgi-generic
359
375
    # python-server-page
360
376
}
361
377
 
362
 
def fixup_environ(req, script_path):
363
 
    """Assuming os.environ has been written with the CGI variables from
364
 
    apache, make a few changes for security and correctness.
 
378
def cgi_environ(req, script_path, user, overrides=None):
 
379
    """Gets CGI variables from apache and makes a few changes for security and 
 
380
    correctness.
365
381
 
366
382
    Does not modify req, only reads it.
 
383
 
 
384
    overrides: A dict mapping env var names to strings, to override arbitrary
 
385
        environment variables in the resulting CGI environent.
367
386
    """
368
 
    env = os.environ
 
387
    env = {}
369
388
    # Comments here are on the heavy side, explained carefully for security
370
389
    # reasons. Please read carefully before making changes.
 
390
    
 
391
    # This automatically asks mod_python to load up the CGI variables into the
 
392
    # environment (which is a good first approximation)
 
393
    for (k,v) in req.get_cgi_environ().items():
 
394
        env[k] = v
371
395
 
372
396
    # Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
373
397
    # exposes unnecessary details about server.
413
437
 
414
438
    # SERVER_SOFTWARE is actually not Apache but IVLE, since we are
415
439
    # custom-making the CGI request.
416
 
    env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
 
440
    env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
417
441
 
418
442
    # Additional environment variables
419
 
    username = split_path(req.path)[0]
 
443
    username = user.login
420
444
    env['HOME'] = os.path.join('/home', username)
421
445
 
 
446
    if overrides is not None:
 
447
        env.update(overrides)
 
448
    return env
 
449
 
422
450
class ExecutionError(Exception):
423
451
    pass
424
452
 
425
 
def execute_raw(user, jail_dir, working_dir, binary, args):
 
453
def execute_raw(config, user, jail_dir, working_dir, binary, args):
426
454
    '''Execute a binary in a user's jail, returning the raw output.
427
455
 
428
456
    The binary is executed in the given working directory with the given
429
457
    args. A tuple of (stdout, stderr) is returned.
430
458
    '''
431
459
 
432
 
    tramp = location_cgi_python
433
 
    tramp_dir = os.path.split(location_cgi_python)[0]
 
460
    tramp = os.path.join(config['paths']['lib'], 'trampoline')
 
461
    tramp_dir = os.path.split(tramp)[0]
434
462
 
435
463
    # Fire up trampoline. Vroom, vroom.
436
 
    proc = subprocess.Popen(
437
 
        [tramp, str(user.unixid), ivle.conf.jail_base,
438
 
         ivle.conf.jail_src_base, ivle.conf.jail_system, jail_dir,
439
 
         working_dir, binary] + args,
 
464
    cmd_line = [tramp, str(user.unixid), config['paths']['jails']['mounts'],
 
465
         config['paths']['jails']['src'],
 
466
         config['paths']['jails']['template'],
 
467
         jail_dir, working_dir, binary] + args
 
468
    # Popen doesn't like unicode strings. It hateses them.
 
469
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
 
470
                for s in cmd_line]
 
471
    proc = subprocess.Popen(cmd_line,
440
472
        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
441
 
        stderr=subprocess.PIPE, cwd=tramp_dir, close_fds=True)
 
473
        stderr=subprocess.PIPE, cwd=tramp_dir, close_fds=True,
 
474
        env={'HOME': os.path.join('/home', user.login),
 
475
             'PATH': PATH,
 
476
             'USER': user.login,
 
477
             'LOGNAME': user.login})
442
478
 
443
479
    (stdout, stderr) = proc.communicate()
444
480
    exitcode = proc.returncode
445
481
 
446
482
    if exitcode != 0:
447
 
        raise ExecutionError('subprocess ended with code %d, stderr %s' %
448
 
                             (exitcode, proc.stderr.read()))
 
483
        raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
 
484
                             (exitcode, stderr))
449
485
    return (stdout, stderr)