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

« back to all changes in this revision

Viewing changes to ivle/interpret.py

  • Committer: David Coles
  • Date: 2010-02-26 12:33:21 UTC
  • Revision ID: coles.david@gmail.com-20100226123321-9v46hs5xhqv1bg21
console: Fix up crash do to passing unixid rather than User when restarting a console that has timed out

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 import db
26
 
from ivle.util import IVLEError, IVLEJailError
27
 
import ivle.conf
 
26
from ivle.util import IVLEError, IVLEJailError, split_path
28
27
 
29
28
import functools
30
29
 
39
38
 
40
39
CGI_BLOCK_SIZE = 65535
41
40
 
42
 
uids = {}
43
 
 
44
 
def get_uid(login):
45
 
    """Get the unix uid corresponding to the given login name.
46
 
       If it is not in the dictionary of uids, then consult the
47
 
       database and retrieve an update of the user table."""
48
 
    global uids
49
 
    if login in uids:
50
 
        return uids[login]
51
 
 
52
 
    conn = db.DB()
53
 
    res = conn.get_all('login', ['login', 'unixid'])
54
 
    def repack(flds):
55
 
        return (flds['login'], flds['unixid'])
56
 
    uids = dict(map(repack,res))
57
 
 
58
 
    return uids[login]
59
 
 
60
41
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
61
42
    """Serves a file by interpreting it using one of IVLE's builtin
62
43
    interpreters. All interpreters are intended to run in the user's jail. The
64
45
    to the individual interpreters to create the jail.
65
46
 
66
47
    req: An IVLE request object.
67
 
    owner: Username of the user who owns the file being served.
 
48
    owner: The user who owns the file being served.
68
49
    jail_dir: Absolute path to the user's jail.
69
50
    filename: Absolute filename within the user's jail.
70
51
    interpreter: A function object to call.
79
60
        filename_abs = os.path.join(os.sep, filename)
80
61
        filename_rel = filename
81
62
 
82
 
    # Get the UID of the owner of the file
83
63
    # (Note: files are executed by their owners, not the logged in user.
84
64
    # This ensures users are responsible for their own programs and also
85
65
    # allows them to be executed by the public).
86
 
    uid = get_uid(owner)
87
66
 
88
67
    # Split up req.path again, this time with respect to the jail
89
68
    (working_dir, _) = os.path.split(filename_abs)
94
73
    # (Note that paths "relative" to the jail actually begin with a '/' as
95
74
    # they are absolute in the jailspace)
96
75
 
97
 
    return interpreter(uid, jail_dir, working_dir, filename_abs, req,
 
76
    return interpreter(owner, jail_dir, working_dir, filename_abs, req,
98
77
                       gentle)
99
78
 
100
79
class CGIFlags:
109
88
        self.linebuf = ""
110
89
        self.headers = {}       # Header names : values
111
90
 
112
 
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
113
 
                script_path, req, gentle):
 
91
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
 
92
                req, gentle):
114
93
    """
115
94
    trampoline: Full path on the local system to the CGI wrapper program
116
95
        being executed.
117
 
    uid: User ID of the owner of the file.
 
96
    owner: User object of the owner of the file.
118
97
    jail_dir: Absolute path of owner's jail directory.
119
98
    working_dir: Directory containing the script file relative to owner's
120
99
        jail.
126
105
    its environment.
127
106
    """
128
107
 
 
108
    trampoline = os.path.join(req.config['paths']['lib'], 'trampoline')
 
109
 
129
110
    # Support no-op trampoline runs.
130
111
    if interpreter is None:
131
112
        interpreter = '/bin/true'
155
136
        del os.environ[k]
156
137
    for (k,v) in req.get_cgi_environ().items():
157
138
        os.environ[k] = v
158
 
    fixup_environ(req)
 
139
    fixup_environ(req, script_path, owner)
159
140
 
160
141
    # usage: tramp uid jail_dir working_dir script_path
161
 
    pid = subprocess.Popen(
162
 
        [trampoline, str(uid), jail_dir, working_dir, interpreter,
163
 
        script_path],
 
142
    cmd_line = [trampoline, str(owner.unixid),
 
143
            req.config['paths']['jails']['mounts'],
 
144
            req.config['paths']['jails']['src'],
 
145
            req.config['paths']['jails']['template'],
 
146
            jail_dir, working_dir, interpreter, script_path]
 
147
    # Popen doesn't like unicode strings. It hateses them.
 
148
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
 
149
                for s in cmd_line]
 
150
    pid = subprocess.Popen(cmd_line,
164
151
        stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
165
152
        cwd=tramp_dir)
166
153
 
362
349
    <pre>
363
350
""" % (warning, text))
364
351
 
365
 
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
366
 
 
367
352
# Mapping of interpreter names (as given in conf/app/server.py) to
368
353
# interpreter functions.
369
354
 
370
355
interpreter_objects = {
371
356
    'cgi-python'
372
 
        : functools.partial(execute_cgi, "/usr/bin/python",
373
 
            location_cgi_python),
 
357
        : functools.partial(execute_cgi, "/usr/bin/python"),
374
358
    'noop'
375
 
        : functools.partial(execute_cgi, None,
376
 
            location_cgi_python),
 
359
        : functools.partial(execute_cgi, None),
377
360
    # Should also have:
378
361
    # cgi-generic
379
362
    # python-server-page
380
363
}
381
364
 
382
 
def fixup_environ(req):
 
365
def fixup_environ(req, script_path, user):
383
366
    """Assuming os.environ has been written with the CGI variables from
384
367
    apache, make a few changes for security and correctness.
385
368
 
405
388
        del env['PATH']
406
389
    except: pass
407
390
 
408
 
    # Remove SCRIPT_FILENAME. Not part of CGI spec (see SCRIPT_NAME).
409
 
 
410
 
    # PATH_INFO is wrong because the script doesn't physically exist.
411
 
    # Apache makes it relative to the "serve" app. It should actually be made
412
 
    # relative to the student's script. intepretservice does that in the jail,
413
 
    # so here we just clear it.
414
 
    env['PATH_INFO'] = ''
415
 
    env['PATH_TRANSLATED'] = ''
416
 
 
417
391
    # CGI specifies that REMOTE_HOST SHOULD be set, and MAY just be set to
418
392
    # REMOTE_ADDR. Since Apache does not appear to set this, set it to
419
393
    # REMOTE_ADDR.
420
394
    if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
421
395
        env['REMOTE_HOST'] = env['REMOTE_ADDR']
422
396
 
 
397
    env['PATH_INFO'] = ''
 
398
    del env['PATH_TRANSLATED']
 
399
 
 
400
    normuri = os.path.normpath(req.uri)
 
401
    env['SCRIPT_NAME'] = normuri
 
402
 
423
403
    # SCRIPT_NAME is the path to the script WITHOUT PATH_INFO.
424
 
    script_name = req.uri
425
 
    env['SCRIPT_NAME'] = script_name
 
404
    # We don't care about these if the script is null (ie. noop).
 
405
    # XXX: We check for /home because we don't want to interfere with
 
406
    # CGIRequest, which fileservice still uses.
 
407
    if script_path and script_path.startswith('/home'):
 
408
        normscript = os.path.normpath(script_path)
 
409
 
 
410
        uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
 
411
 
 
412
        # PATH_INFO is wrong because the script doesn't physically exist.
 
413
        env['PATH_INFO'] = uri_into_jail[len(normscript):]
 
414
        if len(env['PATH_INFO']) > 0:
 
415
            env['SCRIPT_NAME'] = normuri[:-len(env['PATH_INFO'])]
426
416
 
427
417
    # SERVER_SOFTWARE is actually not Apache but IVLE, since we are
428
418
    # custom-making the CGI request.
429
 
    env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
 
419
    env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
430
420
 
431
421
    # Additional environment variables
432
 
    username = studpath.url_to_jailpaths(req.path)[0]
 
422
    username = user.login
433
423
    env['HOME'] = os.path.join('/home', username)
 
424
 
 
425
class ExecutionError(Exception):
 
426
    pass
 
427
 
 
428
def execute_raw(config, user, jail_dir, working_dir, binary, args):
 
429
    '''Execute a binary in a user's jail, returning the raw output.
 
430
 
 
431
    The binary is executed in the given working directory with the given
 
432
    args. A tuple of (stdout, stderr) is returned.
 
433
    '''
 
434
 
 
435
    tramp = os.path.join(config['paths']['lib'], 'trampoline')
 
436
    tramp_dir = os.path.split(tramp)[0]
 
437
 
 
438
    # Fire up trampoline. Vroom, vroom.
 
439
    cmd_line = [tramp, str(user.unixid), config['paths']['jails']['mounts'],
 
440
         config['paths']['jails']['src'],
 
441
         config['paths']['jails']['template'],
 
442
         jail_dir, working_dir, binary] + args
 
443
    # Popen doesn't like unicode strings. It hateses them.
 
444
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
 
445
                for s in cmd_line]
 
446
    proc = subprocess.Popen(cmd_line,
 
447
        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
 
448
        stderr=subprocess.PIPE, cwd=tramp_dir, close_fds=True,
 
449
        env={'HOME': os.path.join('/home', user.login),
 
450
             'PATH': os.environ['PATH'],
 
451
             'USER': user.login,
 
452
             'LOGNAME': user.login})
 
453
 
 
454
    (stdout, stderr) = proc.communicate()
 
455
    exitcode = proc.returncode
 
456
 
 
457
    if exitcode != 0:
 
458
        raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
 
459
                             (exitcode, stderr))
 
460
    return (stdout, stderr)