38
37
# working on smaller output
40
39
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))
40
PATH = "/usr/local/bin:/usr/bin:/bin"
60
42
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
61
43
"""Serves a file by interpreting it using one of IVLE's builtin
110
90
self.headers = {} # Header names : values
112
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
113
script_path, req, gentle):
92
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
115
95
trampoline: Full path on the local system to the CGI wrapper program
117
uid: User ID of the owner of the file.
97
owner: User object of the owner of the file.
118
98
jail_dir: Absolute path of owner's jail directory.
119
99
working_dir: Directory containing the script file relative to owner's
148
130
f.seek(0) # Rewind, for reading
150
132
# Set up the environment
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():
156
for (k,v) in req.get_cgi_environ().items():
133
environ = cgi_environ(req, script_path, owner)
160
135
# usage: tramp uid jail_dir working_dir script_path
161
pid = subprocess.Popen(
162
[trampoline, str(uid), jail_dir, working_dir, interpreter,
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)
144
pid = subprocess.Popen(cmd_line,
164
145
stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
167
# Restore the environment
168
for k in os.environ.keys():
170
for (k,v) in old_env.items():
146
cwd=tramp_dir, env=environ)
173
148
# We don't want any output! Bail out after the process terminates.
240
215
if len(split) == 1:
241
216
split = headers.split('\n', 1)
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'])
218
# If not executing in gentle mode (which presents CGI violations
219
# to users nicely), check if this an internal IVLE error
221
if not cgiflags.gentle:
222
hs = cgiflags.headers
223
if 'X-IVLE-Error-Type' in hs:
252
225
raise IVLEJailError(hs['X-IVLE-Error-Type'],
253
226
hs['X-IVLE-Error-Message'],
254
227
hs['X-IVLE-Error-Info'])
256
raise IVLEError(500, 'bad error headers written by CGI')
229
raise AssertionError("Bad error headers written by CGI.")
258
231
# Check to make sure the required headers were written
259
232
if cgiflags.wrote_html_warning or not cgiflags.gentle:
363
336
""" % (warning, text))
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
367
338
# Mapping of interpreter names (as given in conf/app/server.py) to
368
339
# interpreter functions.
370
341
interpreter_objects = {
372
: functools.partial(execute_cgi, "/usr/bin/python",
373
location_cgi_python),
343
: functools.partial(execute_cgi, "/usr/bin/python"),
375
: functools.partial(execute_cgi, None,
376
location_cgi_python),
345
: functools.partial(execute_cgi, None),
377
346
# Should also have:
379
348
# python-server-page
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.
351
def cgi_environ(req, script_path, user):
352
"""Gets CGI variables from apache and makes a few changes for security and
386
355
Does not modify req, only reads it.
389
358
# Comments here are on the heavy side, explained carefully for security
390
359
# reasons. Please read carefully before making changes.
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():
392
366
# Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
393
367
# exposes unnecessary details about server.
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'] = ''
417
382
# CGI specifies that REMOTE_HOST SHOULD be set, and MAY just be set to
418
383
# REMOTE_ADDR. Since Apache does not appear to set this, set it to
420
385
if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
421
386
env['REMOTE_HOST'] = env['REMOTE_ADDR']
388
env['PATH_INFO'] = ''
389
del env['PATH_TRANSLATED']
391
normuri = os.path.normpath(req.uri)
392
env['SCRIPT_NAME'] = normuri
423
394
# SCRIPT_NAME is the path to the script WITHOUT PATH_INFO.
424
script_name = req.uri
425
env['SCRIPT_NAME'] = script_name
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)
401
uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
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'])]
427
408
# SERVER_SOFTWARE is actually not Apache but IVLE, since we are
428
409
# custom-making the CGI request.
429
env['SERVER_SOFTWARE'] = "IVLE/" + str(ivle.conf.ivle_version)
410
env['SERVER_SOFTWARE'] = "IVLE/" + ivle.__version__
431
412
# Additional environment variables
432
username = studpath.url_to_jailpaths(req.path)[0]
413
username = user.login
433
414
env['HOME'] = os.path.join('/home', username)
418
class ExecutionError(Exception):
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.
424
The binary is executed in the given working directory with the given
425
args. A tuple of (stdout, stderr) is returned.
428
tramp = os.path.join(config['paths']['lib'], 'trampoline')
429
tramp_dir = os.path.split(tramp)[0]
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)
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),
445
'LOGNAME': user.login})
447
(stdout, stderr) = proc.communicate()
448
exitcode = proc.returncode
451
raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
453
return (stdout, stderr)