45
43
CGI_BLOCK_SIZE = 65535
50
"""Get the unix uid corresponding to the given login name.
51
If it is not in the dictionary of uids, then consult the
52
database and retrieve an update of the user table."""
58
res = conn.get_all('login', ['login', 'unixid'])
60
return (flds['login'], flds['unixid'])
61
uids = dict(map(repack,res))
65
def interpret_file(req, owner, jail_dir, filename, interpreter, gentle=True):
45
def interpret_file(req, owner, filename, interpreter):
66
46
"""Serves a file by interpreting it using one of IVLE's builtin
67
47
interpreters. All interpreters are intended to run in the user's jail. The
68
48
jail location is provided as an argument to the interpreter but it is up
71
51
req: An IVLE request object.
72
52
owner: Username of the user who owns the file being served.
73
jail_dir: Absolute path to the user's jail.
74
filename: Absolute filename within the user's jail.
53
filename: Filename in the local file system.
75
54
interpreter: A function object to call.
77
# We can't test here whether or not the target file actually exists,
78
# because the apache user may not have permission. Instead we have to
79
# rely on the interpreter generating an error.
80
if filename.startswith(os.sep):
81
filename_abs = filename
82
filename_rel = filename[1:]
84
filename_abs = os.path.join(os.sep, filename)
85
filename_rel = filename
56
# Make sure the file exists (otherwise some interpreters may not actually
58
# Don't test for execute permission, that will only be required for
59
# certain interpreters.
60
if not os.access(filename, os.R_OK):
61
req.throw_error(req.HTTP_NOT_FOUND)
87
63
# Get the UID of the owner of the file
88
64
# (Note: files are executed by their owners, not the logged in user.
89
65
# This ensures users are responsible for their own programs and also
90
66
# allows them to be executed by the public).
68
(_,_,uid,_,_,_,_) = pwd.getpwnam(owner)
70
# The user does not exist. This should have already failed the
72
req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR)
93
74
# Split up req.path again, this time with respect to the jail
94
(working_dir, _) = os.path.split(filename_abs)
75
(_, jail_dir, path) = studpath.url_to_jailpaths(req.path)
76
path = os.path.join('/', path)
77
(working_dir, _) = os.path.split(path)
95
78
# jail_dir is the absolute jail directory.
96
79
# path is the filename relative to the user's jail.
97
80
# working_dir is the directory containing the file relative to the user's
99
82
# (Note that paths "relative" to the jail actually begin with a '/' as
100
83
# they are absolute in the jailspace)
102
return interpreter(uid, jail_dir, working_dir, filename_abs, req,
85
return interpreter(uid, jail_dir, working_dir, path, req)
106
"""Stores flags regarding the state of reading CGI output.
107
If this is to be gentle, detection of invalid headers will result in an
109
def __init__(self, begentle=True):
110
self.gentle = begentle
88
"""Stores flags regarding the state of reading CGI output."""
111
90
self.started_cgi_body = False
112
91
self.got_cgi_headers = False
113
92
self.wrote_html_warning = False
115
94
self.headers = {} # Header names : values
117
96
def execute_cgi(interpreter, trampoline, uid, jail_dir, working_dir,
118
script_path, req, gentle):
120
99
trampoline: Full path on the local system to the CGI wrapper program
170
149
# process_cgi_line: Reads a single line of CGI output and processes it.
171
150
# Prints to req, and also does fancy HTML warnings if Content-Type
173
cgiflags = CGIFlags(gentle)
152
cgiflags = CGIFlags()
175
154
# Read from the process's stdout into req
176
155
data = pid.stdout.read(CGI_BLOCK_SIZE)
202
181
# Break data into lines of CGI header data.
203
182
linebuf = cgiflags.linebuf + data
204
183
# First see if we can split all header data
205
# We need to get the double CRLF- or LF-terminated headers, whichever
206
# is smaller, as either sequence may appear somewhere in the body.
207
usplit = linebuf.split('\n\n', 1)
208
wsplit = linebuf.split('\r\n\r\n', 1)
209
split = len(usplit[0]) > len(wsplit[0]) and wsplit or usplit
184
split = linebuf.split('\r\n\r\n', 1)
186
# Allow UNIX newlines instead
187
split = linebuf.split('\n\n', 1)
210
188
if len(split) == 1:
211
189
# Haven't seen all headers yet. Buffer and come back later.
212
190
cgiflags.linebuf = linebuf
232
210
if len(split) == 1:
233
211
split = headers.split('\n', 1)
235
# Is this an internal IVLE error condition?
236
hs = cgiflags.headers
237
if 'X-IVLE-Error-Type' in hs:
238
t = hs['X-IVLE-Error-Type']
239
if t == IVLEError.__name__:
240
raise IVLEError(int(hs['X-IVLE-Error-Code']),
241
hs['X-IVLE-Error-Message'])
244
raise IVLEJailError(hs['X-IVLE-Error-Type'],
245
hs['X-IVLE-Error-Message'],
246
hs['X-IVLE-Error-Info'])
248
raise IVLEError(500, 'bad error headers written by CGI')
250
213
# Check to make sure the required headers were written
251
if cgiflags.wrote_html_warning or not cgiflags.gentle:
214
if cgiflags.wrote_html_warning:
252
215
# We already reported an error, that's enough
254
217
elif "Content-Type" in cgiflags.headers:
282
245
name, value = line.split(':', 1)
283
246
except ValueError:
284
247
# No colon. The user did not write valid headers.
285
# If we are being gentle, we want to help the user understand what
286
# went wrong. Otherwise, just admit we screwed up.
288
if not cgiflags.gentle:
289
message = """An unexpected server error has occured."""
291
elif len(cgiflags.headers) == 0:
248
if len(cgiflags.headers) == 0:
292
249
# First line was not a header line. We can assume this is not
294
251
message = """You did not print a CGI header.
300
257
message = """You printed an invalid CGI header. You need to leave
301
258
a blank line after the headers, before writing the page contents."""
302
write_html_warning(req, message, warning=warning)
259
write_html_warning(req, message)
303
260
cgiflags.wrote_html_warning = True
304
261
# Handle the rest of this line as normal data
305
262
process_cgi_output(req, line + '\n', cgiflags)
319
276
req.status = int(value.split(' ', 1)[0])
320
277
except ValueError:
321
if not cgiflags.gentle:
322
# This isn't user code, so it should be good.
323
# Get us out of here!
325
278
message = """The "Status" CGI header was invalid. You need to
326
279
print a number followed by a message, such as "302 Found"."""
327
280
write_html_warning(req, message)
334
287
req.headers_out[name] = value
335
288
cgiflags.headers[name] = value
337
def write_html_warning(req, text, warning="Warning"):
290
def write_html_warning(req, text):
338
291
"""Prints an HTML warning about invalid CGI interaction on the part of the
339
292
user. text may contain HTML markup."""
340
293
req.content_type = "text/html"
348
301
<body style="margin: 0; padding: 0; font-family: sans-serif;">
349
302
<div style="background-color: #faa; border-bottom: 1px solid black;
351
<p><strong>%s</strong>: %s
304
<p><strong>Warning</strong>: %s
353
306
<div style="margin: 8px;">
355
""" % (warning, text))
357
310
location_cgi_python = os.path.join(conf.ivle_install_dir,
358
311
"bin/trampoline")
379
332
# Comments here are on the heavy side, explained carefully for security
380
333
# reasons. Please read carefully before making changes.
335
# Remove HTTP_COOKIE. It is a security risk to have students see the IVLE
336
# cookie of their visitors.
338
del env['HTTP_COOKIE']
382
341
# Remove DOCUMENT_ROOT and SCRIPT_FILENAME. Not part of CGI spec and
383
342
# exposes unnecessary details about server.