39
40
CGI_BLOCK_SIZE = 65535
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."""
53
res = conn.get_all('login', ['login', 'unixid'])
55
return (flds['login'], flds['unixid'])
56
uids = dict(map(repack,res))
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
89
110
self.headers = {} # Header names : values
91
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
112
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
113
script_path, req, gentle):
94
115
trampoline: Full path on the local system to the CGI wrapper program
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
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)
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)
150
pid = subprocess.Popen(cmd_line,
161
pid = subprocess.Popen(
162
[trampoline, str(uid), jail_dir, working_dir, interpreter,
151
164
stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
350
363
""" % (warning, text))
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
352
367
# Mapping of interpreter names (as given in conf/app/server.py) to
353
368
# interpreter functions.
355
370
interpreter_objects = {
357
: functools.partial(execute_cgi, "/usr/bin/python"),
372
: functools.partial(execute_cgi, "/usr/bin/python",
373
location_cgi_python),
359
: functools.partial(execute_cgi, None),
375
: functools.partial(execute_cgi, None,
376
location_cgi_python),
360
377
# Should also have:
362
379
# python-server-page
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.
408
# Remove SCRIPT_FILENAME. Not part of CGI spec (see SCRIPT_NAME).
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'] = ''
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
394
420
if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
395
421
env['REMOTE_HOST'] = env['REMOTE_ADDR']
397
env['PATH_INFO'] = ''
398
del env['PATH_TRANSLATED']
400
normuri = os.path.normpath(req.uri)
401
env['SCRIPT_NAME'] = normuri
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)
410
uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
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
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)
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)
425
class ExecutionError(Exception):
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.
431
The binary is executed in the given working directory with the given
432
args. A tuple of (stdout, stderr) is returned.
435
tramp = os.path.join(config['paths']['lib'], 'trampoline')
436
tramp_dir = os.path.split(tramp)[0]
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)
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'],
452
'LOGNAME': user.login})
454
(stdout, stderr) = proc.communicate()
455
exitcode = proc.returncode
458
raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
460
return (stdout, stderr)