UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

166 lines (141 loc) 5.48 kB
// 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