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}
91
json_response = ivle.chat.chat(host, port, msg, magic,decode=False)
92
# Snoop the response from python-console to check that it's valid
93
response = cjson.decode(json_response)
94
except (cjson.DecodeError, ivle.chat.ProtocolError):
95
# Could not decode the reply from the python-console server
96
response = {"terminate":
97
"Communication to console process lost"}
98
if "terminate" in response:
99
response = restart_console(req.config, uid, jail_path,
100
working_dir, response["terminate"])
101
except socket.error, (enumber, estring):
102
if enumber == errno.ECONNREFUSED:
103
# Timeout: Restart the session
104
response = restart_console(req.config, uid, jail_path,
106
"The IVLE console has timed out due to inactivity")
107
elif enumber == errno.ECONNRESET:
108
# Communication issue: Restart the session
109
response = restart_console(req.config, uid, jail_path,
111
"Connection with the console has been reset")
113
# Some other error - probably serious
114
raise socket.error, (enumber, estring)
118
def restart_console(config, uid, jail_path, working_dir, reason):
148
119
"""Tells the client that it must be issued a new console since the old
149
120
console is no longer availible. The client must accept the new key.
150
121
Returns the JSON response to be given to the client.
152
123
# Start a new console server console
153
cons = console.Console(uid, jail_path, working_dir)
124
cons = ivle.console.Console(config, uid, jail_path, working_dir)
155
126
# Make a JSON object to tell the browser to restart its console client
156
127
new_key = cjson.encode(
157
128
{"host": cons.host, "port": cons.port, "magic": cons.magic})
160
"key": new_key.encode("hex"),
163
return cjson.encode(json_restart)
130
return {"restart": reason, "key": new_key.encode("hex")}