37
38
# working on smaller output
39
40
CGI_BLOCK_SIZE = 65535
40
PATH = "/usr/local/bin:/usr/bin:/bin"
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))
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
90
110
self.headers = {} # Header names : values
92
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):
95
115
trampoline: Full path on the local system to the CGI wrapper program
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
130
148
f.seek(0) # Rewind, for reading
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():
156
for (k,v) in req.get_cgi_environ().items():
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)
144
pid = subprocess.Popen(cmd_line,
161
pid = subprocess.Popen(
162
[trampoline, str(uid), jail_dir, working_dir, interpreter,
145
164
stdin=f, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
146
cwd=tramp_dir, env=environ)
167
# Restore the environment
168
for k in os.environ.keys():
170
for (k,v) in old_env.items():
148
173
# We don't want any output! Bail out after the process terminates.
215
240
if len(split) == 1:
216
241
split = headers.split('\n', 1)
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:
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'])
225
252
raise IVLEJailError(hs['X-IVLE-Error-Type'],
226
253
hs['X-IVLE-Error-Message'],
227
254
hs['X-IVLE-Error-Info'])
229
raise AssertionError("Bad error headers written by CGI.")
256
raise IVLEError(500, 'bad error headers written by CGI')
231
258
# Check to make sure the required headers were written
232
259
if cgiflags.wrote_html_warning or not cgiflags.gentle:
336
363
""" % (warning, text))
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
338
367
# Mapping of interpreter names (as given in conf/app/server.py) to
339
368
# interpreter functions.
341
370
interpreter_objects = {
343
: functools.partial(execute_cgi, "/usr/bin/python"),
372
: functools.partial(execute_cgi, "/usr/bin/python",
373
location_cgi_python),
345
: functools.partial(execute_cgi, None),
375
: functools.partial(execute_cgi, None,
376
location_cgi_python),
346
377
# Should also have:
348
379
# python-server-page
351
def cgi_environ(req, script_path, user):
352
"""Gets CGI variables from apache and makes a few changes for security and
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.
355
386
Does not modify req, only reads it.
358
389
# Comments here are on the heavy side, explained carefully for security
359
390
# 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():
366
392
# Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
367
393
# 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'] = ''
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
385
420
if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
386
421
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
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)
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'])]
424
script_name = req.uri
425
env['SCRIPT_NAME'] = script_name
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)
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)
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)