Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39539
1
###############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2016, Sagemath Inc.
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
###############################################################################
21
22
23
############################################################################
24
#
25
# sage.coffee -- TCP interface between NodeJS and a Sage server instance
26
#
27
# The TCP interface to the sage server is necessarily "weird" because
28
# the Sage process that is actually running the code *is* the server
29
# one talks to via TCP after starting a session. Since Sage itself is
30
# blocking when running code, and running as the user when running
31
# code can't be trusted, e.g., anything in the server can be
32
# arbitrarily modified, all *control* messages, e.g., sending signals,
33
# cleaning up, etc. absolutely require making a separate TCP connection.
34
#
35
# So:
36
#
37
# 1. Make a connection to the TCP server, which runs as root and
38
# forks on connection.
39
#
40
# 2. Create a new session, which drops privileges to a random clean
41
# user, and continues to listen on the TCP socket when not doing
42
# computations.
43
#
44
# 3. Send request-to-exec, etc., messages to the socket in (2)
45
# and get back output over (2).
46
#
47
# 4. To send a signal, get files, save worksheet state, etc.,
48
# make a new connection to the TCP server, and send a message
49
# in the freshly forked off process, which runs as root.
50
#
51
# With this architecture, the sage process that the user is
52
# interacting with has ultimate control over the messages it sends and
53
# receives (which is incredibly powerful and customizable), with no
54
# stupid pexpect or anything else like that to get in the way. At the
55
# same time, we still have a root out-of-process control mechanism,
56
# though with the overhead of establishing a new connection each time.
57
# Since control messages are much less frequent, this overhead is
58
# acceptable.
59
#
60
############################################################################
61
62
net = require('net')
63
winston = require('winston') # https://github.com/flatiron/winston
64
65
message = require("smc-util/message")
66
misc = require('smc-util/misc')
67
{defaults, required} = misc
68
69
{connect_to_locked_socket, enable_mesg} = require('smc-util-node/misc_node')
70
71
exports.send_control_message = (opts) ->
72
opts = defaults opts,
73
host : 'localhost'
74
port : required
75
secret_token : required
76
mesg : required
77
78
sage_control_conn = new exports.Connection
79
secret_token : opts.secret_token
80
host : opts.host
81
port : opts.port
82
cb : ->
83
sage_control_conn.send_json(opts.mesg)
84
sage_control_conn.close()
85
86
exports.send_signal = (opts) ->
87
opts = defaults opts,
88
host : 'localhost'
89
port : required
90
secret_token : required
91
pid : required
92
signal : required
93
94
exports.send_control_message
95
host : opts.host
96
port : opts.port
97
secret_token : opts.secret_token
98
mesg : message.send_signal(pid:opts.pid, signal:opts.signal)
99
100
101
class exports.Connection
102
constructor: (options) ->
103
options = defaults options,
104
secret_token : required
105
port : required
106
host : 'localhost' # should always be there, since we use port forwarding for security
107
recv : undefined
108
cb : undefined
109
@host = options.host
110
@port = options.port
111
112
connect_to_locked_socket
113
port : @port
114
token : options.secret_token
115
cb : (err, _conn) =>
116
if err
117
options.cb(err)
118
return
119
120
if not _conn
121
options.cb("unable to connect to a locked socket")
122
return
123
124
@conn = _conn
125
126
@recv = options.recv # send message to client
127
@buf = null
128
@buf_target_length = -1
129
130
@conn.on 'error', (err) =>
131
winston.error("sage connection error: #{err}")
132
@recv?('json', message.terminate_session(reason:"#{err}"))
133
134
enable_mesg(@conn, 'connection to a sage server')
135
@conn.on 'mesg', (type, data) =>
136
@recv?(type, data)
137
138
options.cb()
139
140
send_json: (mesg) ->
141
@conn?.write_mesg('json', mesg)
142
143
send_blob: (uuid, blob) ->
144
@conn?.write_mesg('blob', {uuid:uuid, blob:blob})
145
146
# Close the connection with the server. You probably instead want
147
# to send_signal(...) using the module-level function to kill the
148
# session, in most cases, since this will leave the Sage process running.
149
close: () ->
150
@conn?.end()
151
@conn?.destroy()
152
153