smc-hub
Version:
CoCalc: Backend webserver component
166 lines (141 loc) • 5.48 kB
JavaScript
// Generated by CoffeeScript 2.5.1
(function() {
//########################################################################
// This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
// License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
//########################################################################
// sage.coffee -- TCP interface between NodeJS and a Sage server instance
// The TCP interface to the sage server is necessarily "weird" because
// the Sage process that is actually running the code *is* the server
// one talks to via TCP after starting a session. Since Sage itself is
// blocking when running code, and running as the user when running
// code can't be trusted, e.g., anything in the server can be
// arbitrarily modified, all *control* messages, e.g., sending signals,
// cleaning up, etc. absolutely require making a separate TCP connection.
// So:
// 1. Make a connection to the TCP server, which runs as root and
// forks on connection.
// 2. Create a new session, which drops privileges to a random clean
// user, and continues to listen on the TCP socket when not doing
// computations.
// 3. Send request-to-exec, etc., messages to the socket in (2)
// and get back output over (2).
// 4. To send a signal, get files, save worksheet state, etc.,
// make a new connection to the TCP server, and send a message
// in the freshly forked off process, which runs as root.
// With this architecture, the sage process that the user is
// interacting with has ultimate control over the messages it sends and
// receives (which is incredibly powerful and customizable), with no
// stupid pexpect or anything else like that to get in the way. At the
// same time, we still have a root out-of-process control mechanism,
// though with the overhead of establishing a new connection each time.
// Since control messages are much less frequent, this overhead is
// acceptable.
var connect_to_locked_socket, defaults, enable_mesg, message, misc, net, required, winston;
net = require('net');
winston = require('./logger').getLogger('sage');
message = require("smc-util/message");
misc = require('smc-util/misc');
({defaults, required} = misc);
({connect_to_locked_socket, enable_mesg} = require('smc-util-node/misc_node'));
exports.send_control_message = function(opts) {
var sage_control_conn;
opts = defaults(opts, {
host: 'localhost',
port: required,
secret_token: required,
mesg: required
});
return sage_control_conn = new exports.Connection({
secret_token: opts.secret_token,
host: opts.host,
port: opts.port,
cb: function() {
sage_control_conn.send_json(opts.mesg);
return sage_control_conn.close();
}
});
};
exports.send_signal = function(opts) {
opts = defaults(opts, {
host: 'localhost',
port: required,
secret_token: required,
pid: required,
signal: required
});
return exports.send_control_message({
host: opts.host,
port: opts.port,
secret_token: opts.secret_token,
mesg: message.send_signal({
pid: opts.pid,
signal: opts.signal
})
});
};
exports.Connection = class Connection {
constructor(options) {
options = defaults(options, {
secret_token: required,
port: required,
host: 'localhost', // should always be there, since we use port forwarding for security
recv: void 0,
cb: void 0
});
this.host = options.host;
this.port = options.port;
connect_to_locked_socket({
port: this.port,
token: options.secret_token,
cb: (err, _conn) => {
if (err) {
options.cb(err);
return;
}
if (!_conn) {
options.cb("unable to connect to a locked socket");
return;
}
this.conn = _conn;
this.recv = options.recv; // send message to client
this.buf = null;
this.buf_target_length = -1;
this.conn.on('error', (err) => {
winston.error(`sage connection error: ${err}`);
return typeof this.recv === "function" ? this.recv('json', message.terminate_session({
reason: `${err}`
})) : void 0;
});
enable_mesg(this.conn, 'connection to a sage server');
this.conn.on('mesg', (type, data) => {
return typeof this.recv === "function" ? this.recv(type, data) : void 0;
});
return options.cb();
}
});
}
send_json(mesg) {
var ref;
return (ref = this.conn) != null ? ref.write_mesg('json', mesg) : void 0;
}
send_blob(uuid, blob) {
var ref;
return (ref = this.conn) != null ? ref.write_mesg('blob', {
uuid: uuid,
blob: blob
}) : void 0;
}
// Close the connection with the server. You probably instead want
// to send_signal(...) using the module-level function to kill the
// session, in most cases, since this will leave the Sage process running.
close() {
var ref, ref1;
if ((ref = this.conn) != null) {
ref.end();
}
return (ref1 = this.conn) != null ? ref1.destroy() : void 0;
}
};
}).call(this);
//# sourceMappingURL=sage.js.map