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

« back to all changes in this revision

Viewing changes to ivle/interpret.py

  • Committer: me at id
  • Date: 2009-01-15 01:06:33 UTC
  • mto: This revision was merged to the branch mainline in revision 1090.
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:branches%2Fstorm:1144
ivle.auth.* now uses Storm, and not ivle.(db|user). This changes the interface
    somewhat (lots of things take a 'store' argument), so fix callers.

ivle.db: Remove user_authenticate; it's now a method of ivle.database.User.

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
25
24
from ivle import studpath
26
 
from ivle.util import IVLEError, IVLEJailError, split_path
 
25
from ivle import db
 
26
from ivle.util import IVLEError, IVLEJailError
 
27
import ivle.conf
27
28
 
28
29
import functools
29
30
 
38
39
 
39
40
CGI_BLOCK_SIZE = 65535
40
41
 
 
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
 
41
60
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
42
61
    """Serves a file by interpreting it using one of IVLE's builtin
43
62
    interpreters. All interpreters are intended to run in the user's jail. The
45
64
    to the individual interpreters to create the jail.
46
65
 
47
66
    req: An IVLE request object.
48
 
    owner: The user who owns the file being served.
 
67
    owner: Username of the user who owns the file being served.
49
68
    jail_dir: Absolute path to the user's jail.
50
69
    filename: Absolute filename within the user's jail.
51
70
    interpreter: A function object to call.
60
79
        filename_abs = os.path.join(os.sep, filename)
61
80
        filename_rel = filename
62
81
 
 
82
    # Get the UID of the owner of the file
63
83
    # (Note: files are executed by their owners, not the logged in user.
64
84
    # This ensures users are responsible for their own programs and also
65
85
    # allows them to be executed by the public).
 
86
    uid = get_uid(owner)
66
87
 
67
88
    # Split up req.path again, this time with respect to the jail
68
89
    (working_dir, _) = os.path.split(filename_abs)
73
94
    # (Note that paths "relative" to the jail actually begin with a '/' as
74
95
    # they are absolute in the jailspace)
75
96
 
76
 
    return interpreter(owner, jail_dir, working_dir, filename_abs, req,
 
97
    return interpreter(uid, jail_dir, working_dir, filename_abs, req,
77
98
                       gentle)
78
99
 
79
100
class CGIFlags:
88
109
        self.linebuf = ""
89
110
        self.headers = {}       # Header names : values
90
111
 
91
 
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
92
 
                req, gentle):
 
112
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
 
113
                script_path, req, gentle):
93
114
    """
94
115
    trampoline: Full path on the local system to the CGI wrapper program
95
116
        being executed.
96
 
    owner: User object of the owner of the file.
 
117
    uid: User ID of the owner of the file.
97
118
    jail_dir: Absolute path of owner's jail directory.
98
119
    working_dir: Directory containing the script file relative to owner's
99
120
        jail.
105
126
    its environment.
106
127
    """
107
128
 
108
 
    trampoline = os.path.join(req.config['paths']['lib'], 'trampoline')
109
 
 
110
129
    # Support no-op trampoline runs.
111
130
    if interpreter is None:
112
131
        interpreter = '/bin/true'
136
155
        del os.environ[k]
137
156
    for (k,v) in req.get_cgi_environ().items():
138
157
        os.environ[k] = v
139
 
    fixup_environ(req, script_path, owner)
 
158
    fixup_environ(req)
140
159
 
141
160
    # usage: tramp uid jail_dir working_dir 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,
 
161
    pid = subprocess.Popen(
 
162
        [trampoline, str(uid), jail_dir, working_dir, interpreter,
 
163
        script_path],
151
164
        stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
152
165
        cwd=tramp_dir)
153
166
 
349
362
    <pre>
350
363
""" % (warning, text))
351
364
 
 
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
 
366
 
352
367
# Mapping of interpreter names (as given in conf/app/server.py) to
353
368
# interpreter functions.
354
369
 
355
370
interpreter_objects = {
356
371
    'cgi-python'
357
 
        : functools.partial(execute_cgi, "/usr/bin/python"),
 
372
        : functools.partial(execute_cgi, "/usr/bin/python",
 
373
            location_cgi_python),
358
374
    'noop'
359
 
        : functools.partial(execute_cgi, None),
 
375
        : functools.partial(execute_cgi, None,
 
376
            location_cgi_python),
360
377
    # Should also have:
361
378
    # cgi-generic
362
379
    # python-server-page
363
380
}
364
381
 
365
 
def fixup_environ(req, script_path, user):
 
382
def fixup_environ(req):
366
383
    """Assuming os.environ has been written with the CGI variables from
367
384
    apache, make a few changes for security and correctness.
368
385
 
388
405
        del env['PATH']
389
406
    except: pass
390
407
 
 
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
 
391
417
    # CGI specifies that REMOTE_HOST SHOULD be set, and MAY just be set to
392
418
    # REMOTE_ADDR. Since Apache does not appear to set this, set it to
393
419
    # REMOTE_ADDR.
394
420
    if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
395
421
        env['REMOTE_HOST'] = env['REMOTE_ADDR']
396
422
 
397
 
    env['PATH_INFO'] = ''
398
 
    del env['PATH_TRANSLATED']
399
 
 
400
 
    normuri = os.path.normpath(req.uri)
401
 
    env['SCRIPT_NAME'] = normuri
402
 
 
403
423
    # SCRIPT_NAME is the path to the script WITHOUT PATH_INFO.
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'])]
 
424
    script_name = req.uri
 
425
    env['SCRIPT_NAME'] = script_name
416
426
 
417
427
    # SERVER_SOFTWARE is actually not Apache but IVLE, since we are
418
428
    # custom-making the CGI request.
419
 
    env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
 
429
    env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
420
430
 
421
431
    # Additional environment variables
422
 
    username = user.login
 
432
    username = studpath.url_to_jailpaths(req.path)[0]
423
433
    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)