~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:
286
313
        process_cgi_output(req, line + '\n', cgiflags)
287
314
        return
288
315
 
289
 
    # Check if CGI field-name is valid
290
 
    CGI_SEPERATORS = set(['(', ')', '<', '>', '@', ',', ';', ':', '\\', '"',
291
 
            '/', '[', ']', '?', '=', '{', '}', ' ', '\t'])
292
 
    if any((char in CGI_SEPERATORS for char in name)):
293
 
        warning = "Warning"
294
 
        if not cgiflags.gentle:
295
 
            message = """An unexpected server error has occured."""
296
 
            warning = "Error"
297
 
        else:
298
 
            # Header contained illegal characters
299
 
            message = """You printed an invalid CGI header. CGI header
300
 
            field-names can not contain any of the following characters: 
301
 
            <code>( ) &lt; &gt; @ , ; : \\ " / [ ] ? = { } <em>SPACE 
302
 
            TAB</em></code>."""
303
 
        write_html_warning(req, message, warning=warning)
304
 
        cgiflags.wrote_html_warning = True
305
 
        # Handle the rest of this line as normal data
306
 
        process_cgi_output(req, line + '\n', cgiflags)
307
 
        return
308
 
 
309
316
    # Read CGI headers
310
317
    value = value.strip()
311
318
    if name == "Content-Type":
355
362
    <pre>
356
363
""" % (warning, text))
357
364
 
 
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
 
366
 
358
367
# Mapping of interpreter names (as given in conf/app/server.py) to
359
368
# interpreter functions.
360
369
 
361
370
interpreter_objects = {
362
371
    'cgi-python'
363
 
        : functools.partial(execute_cgi, "/usr/bin/python"),
 
372
        : functools.partial(execute_cgi, "/usr/bin/python",
 
373
            location_cgi_python),
364
374
    'noop'
365
 
        : functools.partial(execute_cgi, None),
 
375
        : functools.partial(execute_cgi, None,
 
376
            location_cgi_python),
366
377
    # Should also have:
367
378
    # cgi-generic
368
379
    # python-server-page
369
380
}
370
381
 
371
 
def cgi_environ(req, script_path, user):
372
 
    """Gets CGI variables from apache and makes a few changes for security and 
373
 
    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.
374
385
 
375
386
    Does not modify req, only reads it.
376
387
    """
377
 
    env = {}
 
388
    env = os.environ
378
389
    # Comments here are on the heavy side, explained carefully for security
379
390
    # reasons. Please read carefully before making changes.
380
 
    
381
 
    # This automatically asks mod_python to load up the CGI variables into the
382
 
    # environment (which is a good first approximation)
383
 
    for (k,v) in req.get_cgi_environ().items():
384
 
        env[k] = v
385
391
 
386
392
    # Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
387
393
    # exposes unnecessary details about server.
399
405
        del env['PATH']
400
406
    except: pass
401
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
 
402
417
    # CGI specifies that REMOTE_HOST SHOULD be set, and MAY just be set to
403
418
    # REMOTE_ADDR. Since Apache does not appear to set this, set it to
404
419
    # REMOTE_ADDR.
405
420
    if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
406
421
        env['REMOTE_HOST'] = env['REMOTE_ADDR']
407
422
 
408
 
    env['PATH_INFO'] = ''
409
 
    del env['PATH_TRANSLATED']
410
 
 
411
 
    normuri = os.path.normpath(req.uri)
412
 
    env['SCRIPT_NAME'] = normuri
413
 
 
414
423
    # SCRIPT_NAME is the path to the script WITHOUT PATH_INFO.
415
 
    # We don't care about these if the script is null (ie. noop).
416
 
    # XXX: We check for /home because we don't want to interfere with
417
 
    # CGIRequest, which fileservice still uses.
418
 
    if script_path and script_path.startswith('/home'):
419
 
        normscript = os.path.normpath(script_path)
420
 
 
421
 
        uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
422
 
 
423
 
        # PATH_INFO is wrong because the script doesn't physically exist.
424
 
        env['PATH_INFO'] = uri_into_jail[len(normscript):]
425
 
        if len(env['PATH_INFO']) > 0:
426
 
            env['SCRIPT_NAME'] = normuri[:-len(env['PATH_INFO'])]
 
424
    script_name = req.uri
 
425
    env['SCRIPT_NAME'] = script_name
427
426
 
428
427
    # SERVER_SOFTWARE is actually not Apache but IVLE, since we are
429
428
    # custom-making the CGI request.
430
 
    env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
 
429
    env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
431
430
 
432
431
    # Additional environment variables
433
 
    username = user.login
 
432
    username = studpath.url_to_jailpaths(req.path)[0]
434
433
    env['HOME'] = os.path.join('/home', username)
435
 
 
436
 
    return env
437
 
 
438
 
class ExecutionError(Exception):
439
 
    pass
440
 
 
441
 
def execute_raw(config, user, jail_dir, working_dir, binary, args):
442
 
    '''Execute a binary in a user's jail, returning the raw output.
443
 
 
444
 
    The binary is executed in the given working directory with the given
445
 
    args. A tuple of (stdout, stderr) is returned.
446
 
    '''
447
 
 
448
 
    tramp = os.path.join(config['paths']['lib'], 'trampoline')
449
 
    tramp_dir = os.path.split(tramp)[0]
450
 
 
451
 
    # Fire up trampoline. Vroom, vroom.
452
 
    cmd_line = [tramp, str(user.unixid), config['paths']['jails']['mounts'],
453
 
         config['paths']['jails']['src'],
454
 
         config['paths']['jails']['template'],
455
 
         jail_dir, working_dir, binary] + args
456
 
    # Popen doesn't like unicode strings. It hateses them.
457
 
    cmd_line = [(s.encode('utf-8') if isinstance(s, unicode) else s)
458
 
                for s in cmd_line]
459
 
    proc = subprocess.Popen(cmd_line,
460
 
        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
461
 
        stderr=subprocess.PIPE, cwd=tramp_dir, close_fds=True,
462
 
        env={'HOME': os.path.join('/home', user.login),
463
 
             'PATH': PATH,
464
 
             'USER': user.login,
465
 
             'LOGNAME': user.login})
466
 
 
467
 
    (stdout, stderr) = proc.communicate()
468
 
    exitcode = proc.returncode
469
 
 
470
 
    if exitcode != 0:
471
 
        raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
472
 
                             (exitcode, stderr))
473
 
    return (stdout, stderr)