15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
# Author: Matt Giuca, Tom Conway
18
# Author: Matt Giuca, Tom Conway, Will Grant
20
'''Python console RPC service.
22
Provides an HTTP RPC interface to a Python console process.
32
from ivle import (util, studpath, chat, console)
37
"""Handler for the Console Service AJAX backend application."""
38
if len(req.path) > 0 and req.path[-1] == os.sep:
42
# The path determines which "command" we are receiving
43
if req.path == "start":
45
elif req.path == "interrupt":
46
handle_chat(req, kind='interrupt')
47
elif req.path == "restart":
48
handle_chat(req, kind='terminate')
49
elif req.path == "chat":
51
elif req.path == "block":
52
handle_chat(req, kind="block")
53
elif req.path == "flush":
54
handle_chat(req, kind="flush")
55
elif req.path == "inspect":
56
handle_chat(req, kind="inspect")
58
req.throw_error(req.HTTP_BAD_REQUEST)
60
def handle_start(req):
61
# Changes the state on the server - must be POST
62
if req.method != "POST":
63
req.throw_error(req.HTTP_BAD_REQUEST)
65
# See if we have been given extra params
66
fields = req.get_fieldstorage()
68
startdir = fields.getfirst("startdir").value
69
working_dir = os.path.join("/home", req.user.login, startdir)
70
except AttributeError:
71
working_dir = os.path.join("/home", req.user.login)
73
# Get the UID of the logged-in user
76
# Set request attributes
77
req.content_type = "text/plain"
78
req.write_html_head_foot = False
81
jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
82
cons = console.Console(uid, jail_path, working_dir)
84
# Assemble the key and return it.
86
{"host": cons.host, "port": cons.port, "magic": cons.magic})
87
req.write(cjson.encode({"key": key.encode("hex")}))
89
def handle_chat(req, kind = "chat"):
90
# The request *should* have the following four fields:
91
# host, port, magic: Host and port where the console server lives,
92
# and the secret to use to digitally sign the communication with the
94
# text: Fields to pass along to the console server
95
# It simply acts as a proxy to the console server
96
if req.method != "POST":
97
req.throw_error(req.HTTP_BAD_REQUEST)
98
jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
99
working_dir = os.path.join("/home", req.user.login) # Within jail
100
uid = req.user.unixid
101
fields = req.get_fieldstorage()
103
key = cjson.decode(fields.getfirst("key").value.decode("hex"))
107
except AttributeError:
108
# Any of the getfirsts returned None
109
req.throw_error(req.HTTP_BAD_REQUEST)
110
# If text is None, it was probably just an empty line
112
text = fields.getfirst("text").value.decode('utf-8')
113
except AttributeError:
116
msg = {'cmd':kind, 'text':text}
118
response = chat.chat(host, port, msg, magic, decode = False)
120
# Snoop the response from python-console to check that it's valid
122
decoded_response = cjson.decode(response)
123
except cjson.DecodeError:
124
# Could not decode the reply from the python-console server
125
decoded_response = {"terminate":
126
"Communication to console process lost"}
127
if "terminate" in decoded_response:
128
response = restart_console(uid, jail_path, working_dir,
129
decoded_response["terminate"])
131
except socket.error, (enumber, estring):
132
if enumber == errno.ECONNREFUSED:
133
# Timeout: Restart the session
134
response = restart_console(uid, jail_path, working_dir,
135
"The IVLE console has timed out due to inactivity")
136
elif enumber == errno.ECONNRESET:
137
# Communication issue: Restart the session
138
response = restart_console(uid, jail_path, working_dir,
139
"Connection with the console has been reset")
34
from ivle.webapp.base.rest import JSONRESTView, named_operation
35
from ivle.webapp.errors import BadRequest
37
# XXX: Should be RPC view, with actions in URL?
38
class ConsoleServiceRESTView(JSONRESTView):
39
'''An RPC interface to a Python console.'''
40
def get_permissions(self, user, config):
141
# Some other error - probably serious
142
raise socket.error, (enumber, estring)
144
req.content_type = "text/plain"
147
def restart_console(uid, jail_path, working_dir, reason):
46
@named_operation('use')
47
def start(self, req, cwd=''):
48
working_dir = os.path.join("/home", req.user.login, cwd)
53
jail_path = os.path.join(req.config['paths']['jails']['mounts'],
55
cons = ivle.console.Console(req.config, uid, jail_path, working_dir)
57
# Assemble the key and return it. Yes, it is double-encoded.
58
return {'key': cjson.encode({"host": cons.host,
60
"magic": cons.magic}).encode('hex')}
62
@named_operation('use')
63
def chat(self, req, key, text='', cwd='', kind="chat"):
64
# The request *should* have the following four fields:
65
# key: Hex JSON dict of host and port where the console server lives,
66
# and the secret to use to digitally sign the communication with the
68
# text: Fields to pass along to the console server
69
# It simply acts as a proxy to the console server
72
keydict = cjson.decode(key.decode('hex'))
73
host = keydict['host']
74
port = keydict['port']
75
magic = keydict['magic']
77
raise BadRequest("Invalid console key.")
79
jail_path = os.path.join(req.config['paths']['jails']['mounts'],
82
working_dir = os.path.join("/home", req.user.login, cwd)
85
# XXX: JSONRESTView should do this for us.
86
text = text.decode('utf-8')
88
msg = {'cmd':kind, 'text':text}
90
json_response = ivle.chat.chat(host, port, msg, magic,decode=False)
92
# Snoop the response from python-console to check that it's valid
94
response = cjson.decode(json_response)
95
except cjson.DecodeError:
96
# Could not decode the reply from the python-console server
97
response = {"terminate":
98
"Communication to console process lost"}
99
if "terminate" in response:
100
response = restart_console(req.config, uid, jail_path,
101
working_dir, response["terminate"])
102
except socket.error, (enumber, estring):
103
if enumber == errno.ECONNREFUSED:
104
# Timeout: Restart the session
105
response = restart_console(req.config, uid, jail_path,
107
"The IVLE console has timed out due to inactivity")
108
elif enumber == errno.ECONNRESET:
109
# Communication issue: Restart the session
110
response = restart_console(req.config, uid, jail_path,
112
"Connection with the console has been reset")
114
# Some other error - probably serious
115
raise socket.error, (enumber, estring)
119
def restart_console(config, uid, jail_path, working_dir, reason):
148
120
"""Tells the client that it must be issued a new console since the old
149
121
console is no longer availible. The client must accept the new key.
150
122
Returns the JSON response to be given to the client.
152
124
# Start a new console server console
153
cons = console.Console(uid, jail_path, working_dir)
125
cons = ivle.console.Console(config, uid, jail_path, working_dir)
155
127
# Make a JSON object to tell the browser to restart its console client
156
128
new_key = cjson.encode(
157
129
{"host": cons.host, "port": cons.port, "magic": cons.magic})
160
"key": new_key.encode("hex"),
163
return cjson.encode(json_restart)
131
return {"restart": reason, "key": new_key.encode("hex")}