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:
286
313
process_cgi_output(req, line + '\n', cgiflags)
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)):
294
if not cgiflags.gentle:
295
message = """An unexpected server error has occured."""
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>( ) < > @ , ; : \\ " / [ ] ? = { } <em>SPACE
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)
309
316
# Read CGI headers
310
317
value = value.strip()
311
318
if name == "Content-Type":
356
363
""" % (warning, text))
365
location_cgi_python = os.path.join(ivle.conf.lib_path, "trampoline")
358
367
# Mapping of interpreter names (as given in conf/app/server.py) to
359
368
# interpreter functions.
361
370
interpreter_objects = {
363
: functools.partial(execute_cgi, "/usr/bin/python"),
372
: functools.partial(execute_cgi, "/usr/bin/python",
373
location_cgi_python),
365
: functools.partial(execute_cgi, None),
375
: functools.partial(execute_cgi, None,
376
location_cgi_python),
366
377
# Should also have:
368
379
# python-server-page
371
def cgi_environ(req, script_path, user):
372
"""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.
375
386
Does not modify req, only reads it.
378
389
# Comments here are on the heavy side, explained carefully for security
379
390
# reasons. Please read carefully before making changes.
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():
386
392
# Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
387
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'] = ''
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
405
420
if 'REMOTE_HOST' not in env and 'REMOTE_ADDR' in env:
406
421
env['REMOTE_HOST'] = env['REMOTE_ADDR']
408
env['PATH_INFO'] = ''
409
del env['PATH_TRANSLATED']
411
normuri = os.path.normpath(req.uri)
412
env['SCRIPT_NAME'] = normuri
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)
421
uri_into_jail = studpath.to_home_path(os.path.normpath(req.path))
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
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)
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)
438
class ExecutionError(Exception):
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.
444
The binary is executed in the given working directory with the given
445
args. A tuple of (stdout, stderr) is returned.
448
tramp = os.path.join(config['paths']['lib'], 'trampoline')
449
tramp_dir = os.path.split(tramp)[0]
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)
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),
465
'LOGNAME': user.login})
467
(stdout, stderr) = proc.communicate()
468
exitcode = proc.returncode
471
raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
473
return (stdout, stderr)