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)
32
import simplejson as json
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")
38
from ivle.webapp.base.rest import JSONRESTView, write_operation
39
from ivle.webapp.errors import BadRequest
41
# XXX: Should be RPC view, with actions in URL?
42
class ConsoleServiceRESTView(JSONRESTView):
43
'''An RPC interface to a Python console.'''
44
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):
50
@write_operation('use')
51
def start(self, req, cwd=''):
52
working_dir = os.path.join("/home", req.user.login, cwd)
55
jail_path = os.path.join(req.config['paths']['jails']['mounts'],
57
cons = ivle.console.Console(req.config, req.user, jail_path,
60
# Assemble the key and return it. Yes, it is double-encoded.
61
return {'key': json.dumps({"host": cons.host,
63
"magic": cons.magic}).encode('hex')}
65
@write_operation('use')
66
def chat(self, req, key, text='', cwd='', kind="chat"):
67
# The request *should* have the following four fields:
68
# key: Hex JSON dict of host and port where the console server lives,
69
# and the secret to use to digitally sign the communication with the
71
# text: Fields to pass along to the console server
72
# It simply acts as a proxy to the console server
75
keydict = json.loads(key.decode('hex'))
76
host = keydict['host']
77
port = keydict['port']
78
magic = keydict['magic']
80
raise BadRequest("Invalid console key.")
82
jail_path = os.path.join(req.config['paths']['jails']['mounts'],
85
working_dir = os.path.join("/home", req.user.login, cwd)
87
# XXX: JSONRESTView should do this for us.
88
text = text.decode('utf-8')
90
msg = {'cmd':kind, 'text':text}
93
json_response = ivle.chat.chat(host, port, msg, magic,decode=False)
94
# Snoop the response from python-console to check that it's valid
95
response = json.loads(json_response)
96
except (ValueError, ivle.chat.ProtocolError):
97
# Could not decode the reply from the python-console server
98
response = {"terminate":
100
if "terminate" in response:
101
response = restart_console(req.config, req.user, jail_path,
102
working_dir, response["terminate"])
103
except socket.error, (enumber, estring):
104
if enumber == errno.ECONNREFUSED:
105
# Timeout: Restart the session
106
response = restart_console(req.config, req.user, jail_path,
108
"Timed out due to inactivity")
109
elif enumber == errno.ECONNRESET:
110
# Communication issue: Restart the session
111
response = restart_console(req.config, req.user, jail_path,
115
# Some other error - probably serious
116
raise socket.error, (enumber, estring)
120
def restart_console(config, user, jail_path, working_dir, reason):
148
121
"""Tells the client that it must be issued a new console since the old
149
122
console is no longer availible. The client must accept the new key.
150
123
Returns the JSON response to be given to the client.
152
125
# Start a new console server console
153
cons = console.Console(uid, jail_path, working_dir)
126
cons = ivle.console.Console(config, user, jail_path, working_dir)
155
128
# Make a JSON object to tell the browser to restart its console client
156
new_key = cjson.encode(
129
new_key = json.dumps(
157
130
{"host": cons.host, "port": cons.port, "magic": cons.magic})
160
"key": new_key.encode("hex"),
163
return cjson.encode(json_restart)
132
return {"restart": reason, "key": new_key.encode("hex")}