~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 06:11:32 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:1164
ivle.db: Remove get_enrolment and get_subjects_status. They're unused.

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 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
 
37
38
# working on smaller output
38
39
 
39
40
CGI_BLOCK_SIZE = 65535
40
 
PATH = "/usr/local/bin:/usr/bin:/bin"
 
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]
41
59
 
42
60
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
43
61
    """Serves a file by interpreting it using one of IVLE's builtin
46
64
    to the individual interpreters to create the jail.
47
65
 
48
66
    req: An IVLE request object.
49
 
    owner: The user who owns the file being served.
 
67
    owner: Username of the user who owns the file being served.
50
68
    jail_dir: Absolute path to the user's jail.
51
69
    filename: Absolute filename within the user's jail.
52
70
    interpreter: A function object to call.
61
79
        filename_abs = os.path.join(os.sep, filename)
62
80
        filename_rel = filename
63
81
 
 
82
    # Get the UID of the owner of the file
64
83
    # (Note: files are executed by their owners, not the logged in user.
65
84
    # This ensures users are responsible for their own programs and also
66
85
    # allows them to be executed by the public).
 
86
    uid = get_uid(owner)
67
87
 
68
88
    # Split up req.path again, this time with respect to the jail
69
89
    (working_dir, _) = os.path.split(filename_abs)
74
94
    # (Note that paths "relative" to the jail actually begin with a '/' as
75
95
    # they are absolute in the jailspace)
76
96
 
77
 
    return interpreter(owner, jail_dir, working_dir, filename_abs, req,
 
97
    return interpreter(uid, jail_dir, working_dir, filename_abs, req,
78
98
                       gentle)
79
99
 
80
100
class CGIFlags:
89
109
        self.linebuf = ""
90
110
        self.headers = {}       # Header names : values
91
111
 
92
 
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
93
 
                req, gentle):
 
112
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
 
113
                script_path, req, gentle):
94
114
    """
95
115
    trampoline: Full path on the local system to the CGI wrapper program
96
116
        being executed.
97
 
    owner: User object of the owner of the file.
 
117
    uid: User ID of the owner of the file.
98
118
    jail_dir: Absolute path of owner's jail directory.
99
119
    working_dir: Directory containing the script file relative to owner's
100
120
        jail.
106
126
    its environment.
107
127
    """
108
128
 
109
 
    trampoline = os.path.join(req.config['paths']['lib'], 'trampoline')
110
 
 
111
129
    # Support no-op trampoline runs.
112
130
    if interpreter is None:
113
131
        interpreter = '/bin/true'
130
148
        f.seek(0)       # Rewind, for reading
131
149
 
132
150
    # Set up the environment
133
 
    environ = cgi_environ(req, script_path, owner)
 
151
    # This automatically asks mod_python to load up the CGI variables into the
 
152
    # environment (which is a good first approximation)
 
153
    old_env = os.environ.copy()
 
154
    for k in os.environ.keys():
 
155
        del os.environ[k]
 
156
    for (k,v) in req.get_cgi_environ().items():
 
157
        os.environ[k] = v
 
158
    fixup_environ(req)
134
159
 
135
160
    # usage: tramp uid jail_dir working_dir script_path
136
 
    cmd_line = [trampoline, str(owner.unixid),
137
 
            req.config['paths']['jails']['mounts'],
138
 
            req.config['paths']['jails']['src'],
139
 
            req.config['paths']['jails']['template'],
140
 
            jail_dir, working_dir, interpreter, script_path]
141
 
    # Popen doesn't like unicode strings. It hateses them.
142
 
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
143
 
                for s in cmd_line]
144
 
    pid = subprocess.Popen(cmd_line,
 
161
    pid = subprocess.Popen(
 
162
        [trampoline, str(uid), jail_dir, working_dir, interpreter,
 
163
        script_path],
145
164
        stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
146
 
        cwd=tramp_dir, env=environ)
 
165
        cwd=tramp_dir)
 
166
 
 
167
    # Restore the environment
 
168
    for k in os.environ.keys():
 
169
        del os.environ[k]
 
170
    for (k,v) in old_env.items():
 
171
        os.environ[k] = v
147
172
 
148
173
    # We don't want any output! Bail out after the process terminates.
149
174
    if noop:
215
240
            if len(split) == 1:
216
241
                split = headers.split('\n', 1)
217
242
 
218
 
        # If not executing in gentle mode (which presents CGI violations
219
 
        # to users nicely), check if this an internal IVLE error
220
 
        # condition.
221
 
        if not cgiflags.gentle:
222
 
            hs = cgiflags.headers
223
 
            if 'X-IVLE-Error-Type' in hs:
 
243
        # Is this an internal IVLE error condition?
 
244
        hs = cgiflags.headers
 
245
        if 'X-IVLE-Error-Type' in hs:
 
246
            t = hs['X-IVLE-Error-Type']
 
247
            if t == IVLEError.__name__:
 
248
                raise IVLEError(int(hs['X-IVLE-Error-Code']),
 
249
                                hs['X-IVLE-Error-Message'])
 
250
            else:
224
251
                try:
225
252
                    raise IVLEJailError(hs['X-IVLE-Error-Type'],
226
253
                                        hs['X-IVLE-Error-Message'],
227
254
                                        hs['X-IVLE-Error-Info'])
228
255
                except KeyError:
229
 
                    raise AssertionError("Bad error headers written by CGI.")
 
256
                    raise IVLEError(500, 'bad error headers written by CGI')
230
257
 
231
258
        # Check to make sure the required headers were written
232
259
        if cgiflags.wrote_html_warning or not cgiflags.gentle:
335
362
    <pre>
336
363
""" % (warning, text))
337
364
 
 
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
 
366
 
338
367
# Mapping of interpreter names (as given in conf/app/server.py) to
339
368
# interpreter functions.
340
369
 
341
370
interpreter_objects = {
342
371
    'cgi-python'
343
 
        : functools.partial(execute_cgi, "/usr/bin/python"),
 
372
        : functools.partial(execute_cgi, "/usr/bin/python",
 
373
            location_cgi_python),
344
374
    'noop'
345
 
        : functools.partial(execute_cgi, None),
 
375
        : functools.partial(execute_cgi, None,
 
376
            location_cgi_python),
346
377
    # Should also have:
347
378
    # cgi-generic
348
379
    # python-server-page
349
380
}
350
381
 
351
 
def cgi_environ(req, script_path, user):
352
 
    """Gets CGI variables from apache and makes a few changes for security and 
353
 
    correctness.
 
382
def fixup_environ(req):
 
383
    """Assuming os.environ has been written with the CGI variables from
 
384
    apache, make a few changes for security and correctness.
354
385
 
355
386
    Does not modify req, only reads it.
356
387
    """
357
 
    env = {}
 
388
    env = os.environ
358
389
    # Comments here are on the heavy side, explained carefully for security
359
390
    # reasons. Please read carefully before making changes.
360
 
    
361
 
    # This automatically asks mod_python to load up the CGI variables into the
362
 
    # environment (which is a good first approximation)
363
 
    for (k,v) in req.get_cgi_environ().items():
364
 
        env[k] = v
365
391
 
366
392
    # Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
367
393
    # exposes unnecessary details about server.
379
405
        del env['PATH']
380
406
    except: pass
381
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
 
382
417
    # CGI specifies that REMOTE_HOST SHOULD be set, and MAY just be set to
383
418
    # REMOTE_ADDR. Since Apache does not appear to set this, set it to
384
419
    # REMOTE_ADDR.
385
420
    if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
386
421
        env['REMOTE_HOST'] = env['REMOTE_ADDR']
387
422
 
388
 
    env['PATH_INFO'] = ''
389
 
    del env['PATH_TRANSLATED']
390
 
 
391
 
    normuri = os.path.normpath(req.uri)
392
 
    env['SCRIPT_NAME'] = normuri
393
 
 
394
423
    # SCRIPT_NAME is the path to the script WITHOUT PATH_INFO.
395
 
    # We don't care about these if the script is null (ie. noop).
396
 
    # XXX: We check for /home because we don't want to interfere with
397
 
    # CGIRequest, which fileservice still uses.
398
 
    if script_path and script_path.startswith('/home'):
399
 
        normscript = os.path.normpath(script_path)
400
 
 
401
 
        uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
402
 
 
403
 
        # PATH_INFO is wrong because the script doesn't physically exist.
404
 
        env['PATH_INFO'] = uri_into_jail[len(normscript):]
405
 
        if len(env['PATH_INFO']) > 0:
406
 
            env['SCRIPT_NAME'] = normuri[:-len(env['PATH_INFO'])]
 
424
    script_name = req.uri
 
425
    env['SCRIPT_NAME'] = script_name
407
426
 
408
427
    # SERVER_SOFTWARE is actually not Apache but IVLE, since we are
409
428
    # custom-making the CGI request.
410
 
    env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
 
429
    env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
411
430
 
412
431
    # Additional environment variables
413
 
    username = user.login
 
432
    username = studpath.url_to_jailpaths(req.path)[0]
414
433
    env['HOME'] = os.path.join('/home', username)
415
 
 
416
 
    return env
417
 
 
418
 
class ExecutionError(Exception):
419
 
    pass
420
 
 
421
 
def execute_raw(config, user, jail_dir, working_dir, binary, args):
422
 
    '''Execute a binary in a user's jail, returning the raw output.
423
 
 
424
 
    The binary is executed in the given working directory with the given
425
 
    args. A tuple of (stdout, stderr) is returned.
426
 
    '''
427
 
 
428
 
    tramp = os.path.join(config['paths']['lib'], 'trampoline')
429
 
    tramp_dir = os.path.split(tramp)[0]
430
 
 
431
 
    # Fire up trampoline. Vroom, vroom.
432
 
    cmd_line = [tramp, str(user.unixid), config['paths']['jails']['mounts'],
433
 
         config['paths']['jails']['src'],
434
 
         config['paths']['jails']['template'],
435
 
         jail_dir, working_dir, binary] + args
436
 
    # Popen doesn't like unicode strings. It hateses them.
437
 
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
438
 
                for s in cmd_line]
439
 
    proc = subprocess.Popen(cmd_line,
440
 
        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
441
 
        stderr=subprocess.PIPE, cwd=tramp_dir, close_fds=True,
442
 
        env={'HOME': os.path.join('/home', user.login),
443
 
             'PATH': PATH,
444
 
             'USER': user.login,
445
 
             'LOGNAME': user.login})
446
 
 
447
 
    (stdout, stderr) = proc.communicate()
448
 
    exitcode = proc.returncode
449
 
 
450
 
    if exitcode != 0:
451
 
        raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
452
 
                             (exitcode, stderr))
453
 
    return (stdout, stderr)