diff --git a/agents/meshcore.js b/agents/meshcore.js index 8e1270a5..5f5c60c8 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -1212,11 +1212,15 @@ function createMeshCore(agent) { var python = fs.existsSync('/usr/bin/python') ? '/usr/bin/python' : false; var shell = bash || sh; - - var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: { HISTCONTROL: 'ignoreboth', TERM: 'xterm' } }; - var setupcommands = 'alias ls=\'ls --color=auto\'\n'; - if (shell == sh) setupcommands += 'stty erase ^H\n'; - setupcommands += 'clear\n'; + var env = { HISTCONTROL: 'ignoreboth', TERM: 'xterm' }; // LINES: '100', COLUMNS: '100' + if (this.httprequest.xoptions) { + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } + } + var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; + var setupcommands = ' alias ls=\'ls --color=auto\'\n'; + if (shell == sh) setupcommands += ' stty erase ^H\n'; + setupcommands += ' clear\n'; if (script && shell && process.platform == 'linux') { this.httprequest.process = childProcess.execFile(script, ['script', '--return', '--quiet', '-c', '"' + shell + '"', '/dev/null'], options); // Start as active user @@ -1760,94 +1764,119 @@ function createMeshCore(agent) { return; } - if (obj.type == 'options') { - // These are additional connection options passed in the control channel. - //sendConsoleText('options: ' + JSON.stringify(obj)); - delete obj.type; - ws.httprequest.xoptions = obj; - } else if (obj.type == 'close') { - // We received the close on the websocket - //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); - try { ws.close(); } catch (e) { } - } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over. - if (ws.httprequest.protocol == 1) { // Terminal - // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket - if (process.platform == 'win32') { - ws.httprequest._term.unpipe(ws); - } - else { - ws.httprequest.process.stdout.unpipe(ws); - ws.httprequest.process.stderr.unpipe(ws); - } - } else if (ws.httprequest.protocol == 2) { // Desktop - // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket - ws.httprequest.desktop.kvm.unpipe(ws); - } else { - // Switch things around so all WebRTC data goes to onTunnelData(). - ws.rtcchannel.httprequest = ws.httprequest; - ws.rtcchannel.removeAllListeners('data'); - ws.rtcchannel.on('data', onTunnelData); + switch (obj.type) { + case 'options': { + // These are additional connection options passed in the control channel. + //sendConsoleText('options: ' + JSON.stringify(obj)); + delete obj.type; + ws.httprequest.xoptions = obj; + break; } - ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker - } else if (obj.type == 'webrtc1') { - if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal - // Switch the user input from websocket to webrtc at this point. - if (process.platform == 'win32') { - ws.unpipe(ws.httprequest._term); - ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - } - else { - ws.unpipe(ws.httprequest.process.stdin); - ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - } - ws.resume(); // Resume the websocket to keep receiving control data - } else if (ws.httprequest.protocol == 2) { // Desktop - // Switch the user input from websocket to webrtc at this point. - ws.unpipe(ws.httprequest.desktop.kvm); - try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. - ws.resume(); // Resume the websocket to keep receiving control data + case 'close': { + // We received the close on the websocket + //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close'); + try { ws.close(); } catch (e) { } + break; } - ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. - } else if (obj.type == 'webrtc2') { - // Other side received websocket end of data marker, start sending data on WebRTC channel - if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal - if (process.platform == 'win32') { - ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - } - else { - ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - } - } else if (ws.httprequest.protocol == 2) { // Desktop - ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - } - } else if (obj.type == 'offer') { - // This is a WebRTC offer. - if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. - ws.webrtc = rtc.createConnection(); - ws.webrtc.websocket = ws; - ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); - ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); - ws.webrtc.on('dataChannel', function (rtcchannel) { - //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); - rtcchannel.xrtc = this; - rtcchannel.websocket = this.websocket; - this.rtcchannel = rtcchannel; - this.websocket.rtcchannel = rtcchannel; - this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); - this.websocket.rtcchannel.on('end', function () { - // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. - //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); - if (this.websocket.desktop && this.websocket.desktop.kvm) { - this.unpipe(this.websocket.desktop.kvm); - this.websocket.httprequest.desktop.kvm.unpipe(this); + case 'termsize': { + if (ws.httprequest.protocol == 1) { // Terminal + // Indicates a change in terminal size + if (process.platform == 'win32') { + if (ws.httprequest._term == null) return; + //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows); + // TODO + } else { + if (ws.httprequest.process == null) return; + //sendConsoleText('Linux-TermSize: ' + obj.cols + 'x' + obj.rows); + // TODO } + } + break; + } + case 'webrtc0': { // Browser indicates we can start WebRTC switch-over. + if (ws.httprequest.protocol == 1) { // Terminal + // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket + if (process.platform == 'win32') { + ws.httprequest._term.unpipe(ws); + } else { + ws.httprequest.process.stdout.unpipe(ws); + ws.httprequest.process.stderr.unpipe(ws); + } + } else if (ws.httprequest.protocol == 2) { // Desktop + // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket + ws.httprequest.desktop.kvm.unpipe(ws); + } else { + // Switch things around so all WebRTC data goes to onTunnelData(). + ws.rtcchannel.httprequest = ws.httprequest; + ws.rtcchannel.removeAllListeners('data'); + ws.rtcchannel.on('data', onTunnelData); + } + ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker + break; + } + case 'webrtc1': { + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + // Switch the user input from websocket to webrtc at this point. + if (process.platform == 'win32') { + ws.unpipe(ws.httprequest._term); + ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } else { + ws.unpipe(ws.httprequest.process.stdin); + ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + ws.resume(); // Resume the websocket to keep receiving control data + } else if (ws.httprequest.protocol == 2) { // Desktop + // Switch the user input from websocket to webrtc at this point. + ws.unpipe(ws.httprequest.desktop.kvm); + try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (e) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. + ws.resume(); // Resume the websocket to keep receiving control data + } + ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. + break; + } + case 'webrtc2': { + // Other side received websocket end of data marker, start sending data on WebRTC channel + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + if (process.platform == 'win32') { + ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } else { + ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + } + } else if (ws.httprequest.protocol == 2) { // Desktop + ws.httprequest.desktop.kvm.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + } + break; + } + case 'offer': { + // This is a WebRTC offer. + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. + ws.webrtc = rtc.createConnection(); + ws.webrtc.websocket = ws; + ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); + ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); + ws.webrtc.on('dataChannel', function (rtcchannel) { + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); + rtcchannel.xrtc = this; + rtcchannel.websocket = this.websocket; + this.rtcchannel = rtcchannel; + this.websocket.rtcchannel = rtcchannel; + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); + this.websocket.rtcchannel.on('end', function () { + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); + if (this.websocket.desktop && this.websocket.desktop.kvm) { + this.unpipe(this.websocket.desktop.kvm); + this.websocket.httprequest.desktop.kvm.unpipe(this); + } + }); + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. }); - this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. - }); - var sdp = null; - try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { } - if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + var sdp = null; + try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { } + if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } + break; + } } } diff --git a/package.json b/package.json index 554c528a..8ceceaf2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.7-s", + "version": "0.4.7-t", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/common-0.0.1.js b/public/scripts/common-0.0.1.js index ac572a38..b9910de3 100644 --- a/public/scripts/common-0.0.1.js +++ b/public/scripts/common-0.0.1.js @@ -34,8 +34,8 @@ function IntToStrX(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF, (v function MakeToArray(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; } function SplitArray(v) { return v.split(','); } function Clone(v) { return JSON.parse(JSON.stringify(v)); } -function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(//g, '>').replace(/').replace(/\n/g, '').replace(/\t/g, '  '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; } +function EscapeHtml(x) { if (typeof x == 'string') return x.replace(/&/g, '&').replace(/>/g, '>').replace(//g, '>').replace(/').replace(/\n/g, '').replace(/\t/g, '  '); if (typeof x == 'boolean') return x; if (typeof x == 'number') return x; } // Move an element from one position in an array to a new position function ArrayElementMove(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); }; diff --git a/views/xterm.handlebars b/views/xterm.handlebars index d906226e..7e4ce0e4 100644 --- a/views/xterm.handlebars +++ b/views/xterm.handlebars @@ -18,7 +18,7 @@
-
+
@@ -36,7 +36,7 @@
- +
@@ -75,6 +75,7 @@ var authRelayCookie = '{{{authRelayCookie}}}'; var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}'; var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel® AMT Connected"]; + var resizeTimer = null; function start() { // Parse any URL arguments @@ -123,6 +124,12 @@ return obj; } + // Send the new terminal size to the agent + function sendResize() { + resizeTimer = null; + if ((term != null) && (tunnel != null)) { tunnel.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: term.cols, rows: term.rows })); } + } + // Called when the connect/disconnect button is pressed function connectButton() { if (!tunnel) { @@ -133,17 +140,15 @@ if (termfit) { term.loadAddon(termfit); } term.open(Q('terminal')); term.onData(function (data) { if (tunnel != null) { tunnel.sendText(data); } }) - term.onResize(function (size) { - //console.log('Resize', size); - //term.resize(size.cols, size.rows); - //if (tunnel != null) { tunnel.send(String.fromCharCode(27) + '[8;' + size.cols + ';' + size.rows + 't') } - }); + term.onResize(function (size) { if (resizeTimer == null) { resizeTimer = setTimeout(sendResize, 200); } }); if (termfit) { termfit.fit(); } // Setup a terminal tunnel to the agent tunnel = CreateAgentRedirect(meshserver, CreateRemoteTunnel(tunnelUpdate), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); + tunnel.options = { cols: term.cols, rows: term.rows }; tunnel.Start(args.nodeid); tunnel.onStateChanged = onTunnelStateChange; + tunnel.onConsoleMessageChange = function (server, msg) { setConsoleMsg(msg); }; } else { tunnel.Stop(); } @@ -162,9 +167,7 @@ case 0: // Disconnected, clear the terminal term.dispose(); - term = null; - termfit = null; - tunnel = null; + term = termfit = tunnel = null; break; case 3: // Connected @@ -177,6 +180,11 @@ updateButtons(); } + // Console messages + var termConsoleMsgTimer = null; + function clearConsoleMsg() { QV('TermConsoleMsg', false); if (termConsoleMsgTimer) { clearTimeout(termConsoleMsgTimer); termConsoleMsgTimer = null; } } + function setConsoleMsg(msg) { QH('TermConsoleMsg', EscapeHtml(msg).split('\n').join('
')); QV('TermConsoleMsg', true); termConsoleMsgTimer = setTimeout(clearConsoleMsg, 8000); } + // // POPUP DIALOG //