39
40
CGI_BLOCK_SIZE = 65535
40
41
PATH = "/usr/local/bin:/usr/bin:/bin"
42
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
43
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True,
43
45
"""Serves a file by interpreting it using one of IVLE's builtin
44
46
interpreters. All interpreters are intended to run in the user's jail. The
45
47
jail location is provided as an argument to the interpreter but it is up
50
52
jail_dir: Absolute path to the user's jail.
51
53
filename: Absolute filename within the user's jail.
52
54
interpreter: A function object to call.
56
overrides: A dict mapping env var names to strings, to override arbitrary
57
environment variables in the resulting CGI environent.
54
59
# We can't test here whether or not the target file actually exists,
55
60
# because the apache user may not have permission. Instead we have to
90
95
self.headers = {} # Header names : values
92
97
def execute_cgi(interpreter, owner, jail_dir, working_dir, script_path,
98
req, gentle, overrides=None):
95
100
trampoline: Full path on the local system to the CGI wrapper program
130
138
f.seek(0) # Rewind, for reading
132
140
# Set up the environment
133
environ = cgi_environ(req, script_path, owner)
141
environ = cgi_environ(req, script_path, owner, overrides=overrides)
135
143
# usage: tramp uid jail_dir working_dir script_path
136
144
cmd_line = [trampoline, str(owner.unixid),
286
294
process_cgi_output(req, line + '\n', cgiflags)
297
# Check if CGI field-name is valid
298
CGI_SEPERATORS = set(['(', ')', '<', '>', '@', ',', ';', ':', '\\', '"',
299
'/', '[', ']', '?', '=', '{', '}', ' ', '\t'])
300
if any((char in CGI_SEPERATORS for char in name)):
302
if not cgiflags.gentle:
303
message = """An unexpected server error has occured."""
306
# Header contained illegal characters
307
message = """You printed an invalid CGI header. CGI header
308
field-names can not contain any of the following characters:
309
<code>( ) < > @ , ; : \\ " / [ ] ? = { } <em>SPACE
311
write_html_warning(req, message, warning=warning)
312
cgiflags.wrote_html_warning = True
313
# Handle the rest of this line as normal data
314
process_cgi_output(req, line + '\n', cgiflags)
289
317
# Read CGI headers
290
318
value = value.strip()
291
319
if name == "Content-Type":
348
376
# python-server-page
351
def cgi_environ(req, script_path, user):
379
def cgi_environ(req, script_path, user, overrides=None):
352
380
"""Gets CGI variables from apache and makes a few changes for security and
355
383
Does not modify req, only reads it.
385
overrides: A dict mapping env var names to strings, to override arbitrary
386
environment variables in the resulting CGI environent.
358
389
# Comments here are on the heavy side, explained carefully for security
451
484
raise ExecutionError('subprocess ended with code %d, stderr: "%s"' %
452
485
(exitcode, stderr))
453
486
return (stdout, stderr)
488
def jail_call(req, cgi_script, script_name, query_string=None,
489
request_method="GET", extra_overrides=None):
491
Makes a call to a CGI script inside the jail from outside the jail.
492
This can be used to allow Python scripts to access jail-only functions and
493
data without having to perform a full API request.
495
req: A Request object (will not be written to or attributes modified).
496
cgi_script: Path to cgi script outside of jail.
497
eg: os.path.join(req.config['paths']['share'],
498
'services/fileservice')
499
script_name: Name to set as SCRIPT_NAME for the CGI environment.
501
query_string: Query string to set as QUERY_STRING for the CGI environment.
502
eg: "action=svnrepostat&path=/users/studenta/"
503
request_method: Method to set as REQUEST_METHOD for the CGI environment.
504
eg: "POST". Defaults to "GET".
505
extra_overrides: A dict mapping env var names to strings, to override
506
arbitrary environment variables in the resulting CGI environent.
508
Returns a triple (status_code, content_type, contents).
510
interp_object = interpreter_objects["cgi-python"]
511
user_jail_dir = os.path.join(req.config['paths']['jails']['mounts'],
514
"SCRIPT_NAME": script_name,
515
"QUERY_STRING": query_string,
516
"REQUEST_URI": "%s%s%s" % (script_name, "?" if query_string else "",
518
"REQUEST_METHOD": request_method,
520
if extra_overrides is not None:
521
overrides.update(extra_overrides)
522
result = DummyReq(req)
523
interpret_file(result, req.user, user_jail_dir, cgi_script, interp_object,
524
gentle=False, overrides=overrides)
525
return result.status, result.content_type, result.getvalue()
527
class DummyReq(StringIO.StringIO):
528
"""A dummy request object, built from a real request object, which can be
529
used like a req but doesn't mutate the existing request.
530
(Used for reading CGI responses as strings rather than forwarding their
531
output to the current request.)
533
def __init__(self, req):
534
StringIO.StringIO.__init__(self)
536
def get_cgi_environ(self):
537
return self._real_req.get_cgi_environ()
538
def __getattr__(self, name):
539
return getattr(self._real_req, name)