diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index c3df4ea3..ef017ff3 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -21,8 +21,16 @@ + + + + + + + + diff --git a/agents/meshcore.js b/agents/meshcore.js index 14b319cb..4b57846f 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -719,6 +719,7 @@ function createMeshCore(agent) { // Other side received websocket end of data marker, start sending data on WebRTC channel if (this.httprequest.protocol == 1) { // Terminal this.httprequest.process.stdout.pipe(this.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + this.httprequest.process.stderr.pipe(this.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } else if (this.httprequest.protocol == 2) { // Desktop this.httprequest.desktop.kvm.pipe(this.webrtc.rtcchannel, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } @@ -731,12 +732,15 @@ function createMeshCore(agent) { this.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.rtcchannel.on('data', onTunnelWebRTCControlData); - this.rtcchannel.on('end', function () { sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); }); + this.websocket.rtcchannel = rtcchannel; + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); + this.websocket.rtcchannel.on('end', function () { sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); }); if (this.websocket.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 this.websocket.httprequest.process.stdout.unpipe(this.websocket); + this.websocket.httprequest.process.stderr.unpipe(this.websocket); this.websocket.write("{\"type\":\"webrtc1\"}"); // End of data marker } else if (this.websocket.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 diff --git a/agents/modules_meshcmd/amt-0.2.0.js b/agents/modules_meshcmd/amt-0.2.0.js index 9c825179..8780d23d 100644 --- a/agents/modules_meshcmd/amt-0.2.0.js +++ b/agents/modules_meshcmd/amt-0.2.0.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + /** * @fileoverview Intel(r) AMT Communication StackXX * @author Ylian Saint-Hilaire diff --git a/agents/modules_meshcmd/amt-scanner.js b/agents/modules_meshcmd/amt-scanner.js index d6f99fa1..d82b6d8c 100644 --- a/agents/modules_meshcmd/amt-scanner.js +++ b/agents/modules_meshcmd/amt-scanner.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + /** * @description Meshcentral Intel AMT Local Scanner * @author Ylian Saint-Hilaire & Joko Sastriawan diff --git a/agents/modules_meshcmd/amt-script-0.2.0.js b/agents/modules_meshcmd/amt-script-0.2.0.js index 3ebfc703..6d5ec6ac 100644 --- a/agents/modules_meshcmd/amt-script-0.2.0.js +++ b/agents/modules_meshcmd/amt-script-0.2.0.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + /** * @fileoverview Script Compiler / Decompiler / Runner * @author Ylian Saint-Hilaire diff --git a/agents/modules_meshcmd/amt-wsman-0.2.0.js b/agents/modules_meshcmd/amt-wsman-0.2.0.js index 3cb3b35a..4ad00aa0 100644 --- a/agents/modules_meshcmd/amt-wsman-0.2.0.js +++ b/agents/modules_meshcmd/amt-wsman-0.2.0.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + /** * @description Intel(r) AMT WSMAN Stack * @author Ylian Saint-Hilaire diff --git a/agents/modules_meshcmd/amt-wsman-duk-0.2.0.js b/agents/modules_meshcmd/amt-wsman-duk-0.2.0.js index c3e7843d..d82badd7 100644 --- a/agents/modules_meshcmd/amt-wsman-duk-0.2.0.js +++ b/agents/modules_meshcmd/amt-wsman-duk-0.2.0.js @@ -1,4 +1,20 @@ -/** +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** * @description WSMAN communication using duktape http * @author Ylian Saint-Hilaire * @version v0.2.0c diff --git a/agents/modules_meshcmd/amt_heci.js b/agents/modules_meshcmd/amt_heci.js index 99c7a1f0..970e0271 100644 --- a/agents/modules_meshcmd/amt_heci.js +++ b/agents/modules_meshcmd/amt_heci.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + var Q = require('queue'); function amt_heci() { diff --git a/agents/modules_meshcmd/lme_heci.js b/agents/modules_meshcmd/lme_heci.js index 8b619e72..c8f93eb7 100644 --- a/agents/modules_meshcmd/lme_heci.js +++ b/agents/modules_meshcmd/lme_heci.js @@ -1,3 +1,18 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ var MemoryStream = require('MemoryStream'); var lme_id = 0; @@ -151,7 +166,7 @@ function lme_heci() { } this[name][port] = require('net').createServer(); this[name][port].HECI = this; - this[name][port].listen({ port: port }); + this[name][port].listen({ port: port, host: '127.0.0.1' }); this[name][port].on('connection', function (socket) { //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort); this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort); diff --git a/agents/modules_meshcore/WifiScanner.js b/agents/modules_meshcore/WifiScanner.js index 07e1c173..bfc83898 100644 --- a/agents/modules_meshcore/WifiScanner.js +++ b/agents/modules_meshcore/WifiScanner.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + var MemoryStream = require('MemoryStream'); var WindowsWireless = new Buffer([ 0x0A, 0x66, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x5F, 0x53, 0x63, 0x61, 0x6E, 0x28, 0x29, 0x0A, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x77, 0x6C, 0x61, 0x6E, diff --git a/agents/modules_meshcore/amt-scanner.js b/agents/modules_meshcore/amt-scanner.js index 9474dfb0..072377e7 100644 --- a/agents/modules_meshcore/amt-scanner.js +++ b/agents/modules_meshcore/amt-scanner.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + /** * @description Meshcentral Intel AMT Local Scanner * @author Ylian Saint-Hilaire & Joko Sastriawan diff --git a/agents/modules_meshcore/amt_heci.js b/agents/modules_meshcore/amt_heci.js index 99c7a1f0..970e0271 100644 --- a/agents/modules_meshcore/amt_heci.js +++ b/agents/modules_meshcore/amt_heci.js @@ -1,3 +1,19 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + var Q = require('queue'); function amt_heci() { diff --git a/agents/modules_meshcore/lme_heci.js b/agents/modules_meshcore/lme_heci.js index 2ad039b4..c8f93eb7 100644 --- a/agents/modules_meshcore/lme_heci.js +++ b/agents/modules_meshcore/lme_heci.js @@ -1,3 +1,18 @@ +/* +Copyright 2018 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ var MemoryStream = require('MemoryStream'); var lme_id = 0; @@ -21,8 +36,7 @@ var APF_CHANNEL_CLOSE = 97; var APF_PROTOCOLVERSION = 192; -function lme_object() -{ +function lme_object() { this.ourId = ++lme_id; this.amtId = -1; this.LME_CHANNEL_STATUS = 'LME_CS_FREE'; @@ -32,12 +46,11 @@ function lme_object() this.errorCount = 0; } -function stream_bufferedWrite() -{ +function stream_bufferedWrite() { var emitterUtils = require('events').inherits(this); this.buffer = []; this._readCheckImmediate = undefined; - + // Writable Events emitterUtils.createEvent('close'); emitterUtils.createEvent('drain'); @@ -45,21 +58,17 @@ function stream_bufferedWrite() emitterUtils.createEvent('finish'); emitterUtils.createEvent('pipe'); emitterUtils.createEvent('unpipe'); - + // Readable Events emitterUtils.createEvent('readable'); - this.isEmpty = function () - { + this.isEmpty = function () { return (this.buffer.length == 0); }; - this.isWaiting = function () - { + this.isWaiting = function () { return (this._readCheckImmediate == undefined); }; - this.write = function (chunk) - { - for (var args in arguments) - { + this.write = function (chunk) { + for (var args in arguments) { if (typeof (arguments[args]) == 'function') { this.once('drain', arguments[args]); break; } } var tmp = Buffer.alloc(chunk.length); @@ -68,41 +77,34 @@ function stream_bufferedWrite() this.emit('readable'); return (this.buffer.length == 0 ? true : false); }; - this.read = function () - { + this.read = function () { var size = arguments.length == 0 ? undefined : arguments[0]; var bytesRead = 0; var list = []; - while((size == undefined || bytesRead < size) && this.buffer.length > 0) - { + while ((size == undefined || bytesRead < size) && this.buffer.length > 0) { var len = this.buffer[0].data.length - this.buffer[0].offset; var offset = this.buffer[0].offset; - - if(len > (size - bytesRead)) - { + + if (len > (size - bytesRead)) { // Only reading a subset list.push(this.buffer[0].data.slice(offset, offset + size - bytesRead)); this.buffer[0].offset += (size - bytesRead); bytesRead += (size - bytesRead); } - else - { + else { // Reading the entire thing list.push(this.buffer[0].data.slice(offset)); bytesRead += len; this.buffer.shift(); } } - this._readCheckImmediate = setImmediate(function (buffered) - { + this._readCheckImmediate = setImmediate(function (buffered) { buffered._readCheckImmediate = undefined; - if(buffered.buffer.length == 0) - { + if (buffered.buffer.length == 0) { // drained buffered.emit('drain'); } - else - { + else { // not drained buffered.emit('readable'); } @@ -112,38 +114,33 @@ function stream_bufferedWrite() } -function lme_heci() -{ +function lme_heci() { var emitterUtils = require('events').inherits(this); emitterUtils.createEvent('error'); emitterUtils.createEvent('connect'); - + var heci = require('heci'); this.INITIAL_RXWINDOW_SIZE = 4096; - + this._LME = heci.create(); this._LME.LMS = this; this._LME.on('error', function (e) { this.LMS.emit('error', e); }); - this._LME.on('connect', function () - { + this._LME.on('connect', function () { this.LMS.emit('connect'); - this.on('data', function (chunk) - { + this.on('data', function (chunk) { // this = HECI var cmd = chunk.readUInt8(0); - - switch(cmd) - { + + switch (cmd) { default: //console.log('Received ' + chunk.length + ' bytes of data for LMS'); //console.log('Command = ' + cmd); break; - case APF_SERVICE_REQUEST: + case APF_SERVICE_REQUEST: var nameLen = chunk.readUInt32BE(1); var name = chunk.slice(5, nameLen + 5); //console.log("Service Request for: " + name); - if (name == 'pfwd@amt.intel.com' || name == 'auth@amt.intel.com') - { + if (name == 'pfwd@amt.intel.com' || name == 'auth@amt.intel.com') { var outBuffer = Buffer.alloc(5 + nameLen); outBuffer.writeUInt8(6, 0); outBuffer.writeUInt32BE(nameLen, 1); @@ -151,30 +148,26 @@ function lme_heci() this.write(outBuffer); //console.log('Answering APF_SERVICE_REQUEST'); } - else - { + else { //console.log('UNKNOWN APF_SERVICE_REQUEST'); } break; - case APF_GLOBAL_REQUEST: + case APF_GLOBAL_REQUEST: var nameLen = chunk.readUInt32BE(1); var name = chunk.slice(5, nameLen + 5).toString(); - switch(name) - { + switch (name) { case 'tcpip-forward': var len = chunk.readUInt32BE(nameLen + 6); var port = chunk.readUInt32BE(nameLen + 10 + len); //console.log("[" + chunk.length + "/" + len + "] APF_GLOBAL_REQUEST for: " + name + " on port " + port); - if (this[name] == undefined) - { + if (this[name] == undefined) { this[name] = {}; } this[name][port] = require('net').createServer(); this[name][port].HECI = this; - this[name][port].listen({ port: port }); - this[name][port].on('connection', function (socket) - { + this[name][port].listen({ port: port, host: '127.0.0.1' }); + this[name][port].on('connection', function (socket) { //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort); this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort); }); @@ -192,54 +185,48 @@ function lme_heci() break; } break; - case APF_CHANNEL_OPEN_CONFIRMATION: - var rChannel = chunk.readUInt32BE(1); - var sChannel = chunk.readUInt32BE(5); - var wSize = chunk.readUInt32BE(9); - //console.log('rChannel/' + rChannel + ', sChannel/' + sChannel + ', wSize/' + wSize); - if (this.sockets[rChannel] != undefined) - { - this.sockets[rChannel].lme.amtId = sChannel; - this.sockets[rChannel].lme.rxWindow = wSize; - this.sockets[rChannel].lme.txWindow = wSize; - this.sockets[rChannel].lme.LME_CHANNEL_STATUS = 'LME_CS_CONNECTED'; - //console.log('LME_CS_CONNECTED'); - this.sockets[rChannel].bufferedStream = new stream_bufferedWrite(); - this.sockets[rChannel].bufferedStream.socket = this.sockets[rChannel]; - this.sockets[rChannel].bufferedStream.on('readable', function () - { - if(this.socket.lme.txWindow > 0) - { - var buffer = this.read(this.socket.lme.txWindow); - var packet = Buffer.alloc(9 + buffer.length); - packet.writeUInt8(APF_CHANNEL_DATA, 0); - packet.writeUInt32BE(this.socket.lme.amtId, 1); - packet.writeUInt32BE(buffer.length, 5); - buffer.copy(packet, 9); - this.socket.lme.txWindow -= buffer.length; - this.socket.HECI.write(packet); - } - }); - this.sockets[rChannel].bufferedStream.on('drain', function () - { - this.socket.resume(); - }); - this.sockets[rChannel].on('data', function (chunk) - { - if (!this.bufferedStream.write(chunk)) { this.pause(); } - }); - this.sockets[rChannel].on('end', function () - { - var outBuffer = Buffer.alloc(5); - outBuffer.writeUInt8(APF_CHANNEL_CLOSE, 0); - outBuffer.writeUInt32BE(this.lme.amtId, 1); - this.HECI.write(outBuffer); - }); - this.sockets[rChannel].resume(); - } - - break; - case APF_PROTOCOLVERSION: + case APF_CHANNEL_OPEN_CONFIRMATION: + var rChannel = chunk.readUInt32BE(1); + var sChannel = chunk.readUInt32BE(5); + var wSize = chunk.readUInt32BE(9); + //console.log('rChannel/' + rChannel + ', sChannel/' + sChannel + ', wSize/' + wSize); + if (this.sockets[rChannel] != undefined) { + this.sockets[rChannel].lme.amtId = sChannel; + this.sockets[rChannel].lme.rxWindow = wSize; + this.sockets[rChannel].lme.txWindow = wSize; + this.sockets[rChannel].lme.LME_CHANNEL_STATUS = 'LME_CS_CONNECTED'; + //console.log('LME_CS_CONNECTED'); + this.sockets[rChannel].bufferedStream = new stream_bufferedWrite(); + this.sockets[rChannel].bufferedStream.socket = this.sockets[rChannel]; + this.sockets[rChannel].bufferedStream.on('readable', function () { + if (this.socket.lme.txWindow > 0) { + var buffer = this.read(this.socket.lme.txWindow); + var packet = Buffer.alloc(9 + buffer.length); + packet.writeUInt8(APF_CHANNEL_DATA, 0); + packet.writeUInt32BE(this.socket.lme.amtId, 1); + packet.writeUInt32BE(buffer.length, 5); + buffer.copy(packet, 9); + this.socket.lme.txWindow -= buffer.length; + this.socket.HECI.write(packet); + } + }); + this.sockets[rChannel].bufferedStream.on('drain', function () { + this.socket.resume(); + }); + this.sockets[rChannel].on('data', function (chunk) { + if (!this.bufferedStream.write(chunk)) { this.pause(); } + }); + this.sockets[rChannel].on('end', function () { + var outBuffer = Buffer.alloc(5); + outBuffer.writeUInt8(APF_CHANNEL_CLOSE, 0); + outBuffer.writeUInt32BE(this.lme.amtId, 1); + this.HECI.write(outBuffer); + }); + this.sockets[rChannel].resume(); + } + + break; + case APF_PROTOCOLVERSION: var major = chunk.readUInt32BE(1); var minor = chunk.readUInt32BE(5); var reason = chunk.readUInt32BE(9); @@ -254,16 +241,13 @@ function lme_heci() case APF_CHANNEL_WINDOW_ADJUST: var rChannelId = chunk.readUInt32BE(1); var bytesToAdd = chunk.readUInt32BE(5); - if (this.sockets[rChannelId] != undefined) - { + if (this.sockets[rChannelId] != undefined) { this.sockets[rChannelId].lme.txWindow += bytesToAdd; - if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting()) - { + if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting()) { this.sockets[rChannelId].bufferedStream.emit('readable'); } } - else - { + else { //console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_WINDOW_ADJUST'); } break; @@ -271,11 +255,9 @@ function lme_heci() var rChannelId = chunk.readUInt32BE(1); var dataLen = chunk.readUInt32BE(5); var data = chunk.slice(9, 9 + dataLen); - if (this.sockets[rChannelId] != undefined) - { + if (this.sockets[rChannelId] != undefined) { this.sockets[rChannelId].pendingBytes.push(data.length); - this.sockets[rChannelId].write(data, function () - { + this.sockets[rChannelId].write(data, function () { var written = this.pendingBytes.shift(); var outBuffer = Buffer.alloc(9); outBuffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0); @@ -284,35 +266,31 @@ function lme_heci() this.HECI.write(outBuffer); }); } - else - { + else { //console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_DATA'); } break; case APF_CHANNEL_CLOSE: var rChannelId = chunk.readUInt32BE(1); - if (this.sockets[rChannelId] != undefined) - { - this.sockets[rChannelId].end(); + if (this.sockets[rChannelId] != undefined) { + this.sockets[rChannelId].end(); var amtId = this.sockets[rChannelId].lme.amtId; var buffer = Buffer.alloc(5); delete this.sockets[rChannelId]; - + buffer.writeUInt8(APF_CHANNEL_CLOSE, 0); buffer.writeUInt32BE(amtId, 1); this.write(buffer); } - else - { + else { //console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_CLOSE'); } break; } }); }); - - this.bindDuplexStream = function (duplexStream, remoteFamily, localPort) - { + + this.bindDuplexStream = function (duplexStream, remoteFamily, localPort) { var socket = duplexStream; //console.log('New [' + remoteFamily + '] Virtual Connection/' + socket.localPort); socket.pendingBytes = []; @@ -327,19 +305,16 @@ function lme_heci() buffer.writeUInt32BE(socket.lme.ourId); buffer.writeUInt32BE(this.INITIAL_RXWINDOW_SIZE); buffer.writeUInt32BE(0xFFFFFFFF); - for (var i = 0; i < 2; ++i) - { - if (remoteFamily == 'IPv6') - { + for (var i = 0; i < 2; ++i) { + if (remoteFamily == 'IPv6') { buffer.writeUInt32BE(3); buffer.write('::1'); } - else - { + else { buffer.writeUInt32BE(9); buffer.write('127.0.0.1'); } - + buffer.writeUInt32BE(localPort); } this._LME.write(buffer.buffer); @@ -347,7 +322,7 @@ function lme_heci() this._LME.sockets[socket.lme.ourId] = socket; socket.pause(); }; - + this._LME.connect(heci.GUIDS.LME, { noPipeline: 0 }); } diff --git a/meshagent.js b/meshagent.js index b4ec105b..ef1574a1 100644 --- a/meshagent.js +++ b/meshagent.js @@ -148,10 +148,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { } } else if (cmdid == 15) { // MeshCommand_AgentTag - ChangeAgentTag(msg.substring(2)); + var tag = msg.substring(2); + while (tag.charCodeAt(tag.length - 1) == 0) { tag = tag.substring(0, tag.length - 1); } // Remove end-of-line zeros. + ChangeAgentTag(tag); } - } - else if (obj.authenticated < 2) { // We are not authenticated + } else if (obj.authenticated < 2) { // We are not authenticated var cmd = obj.common.ReadShort(msg, 0); if (cmd == 1) { // Agent authentication request diff --git a/meshmail.js b/meshmail.js index b29f5738..c2008656 100644 --- a/meshmail.js +++ b/meshmail.js @@ -26,6 +26,14 @@ module.exports.CreateMeshMain = function (parent) { var accountResetMailHtml = '
[[[SERVERNAME]]] - Verification

Hi [[[USERNAME]]], [[[SERVERNAME]]] is requesting an account password reset, click on the following link to complete the process.

Click here to reset your account password.

If you did not initiate this request, please ignore this mail.
'; var accountResetMailText = '[[[SERVERNAME]]] - Account Reset\r\n\r\nHi [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]]) is requesting an account password reset. Nagivate to the following link to complete the process: [[[CALLBACKURL]]]\r\nIf you did not initiate this request, please ignore this mail.\r\n'; + // Default account invite mail + var accountInviteSubject = '[[[SERVERNAME]]] - Agent Installation Invitation'; + var accountInviteMailHtml = '
[[[SERVERNAME]]] - Agent Installation

User [[[USERNAME]]] on server [[[SERVERNAME]]] is requesting that you install a remote management agent. WARNING: this will allow the requester to take control of your computer. If you wish to do this, click on the following link to download the agent.

Click here to download the MeshAgent for Windows.

If you did not know about this request, please ignore this mail.
'; + var accountInviteMailText = '[[[SERVERNAME]]] - Agent Installation Invitation\r\n\r\nUser [[[USERNAME]]] on server [[[SERVERNAME]]] ([[[SERVERURL]]]) is requesting you install a remote management agent. WARNING: This will allow the requester to take control of your computer. If you wish to do this, click on the following link to download the agent: [[[CALLBACKURL]]]\r\nIf you do not know about this request, please ignore this mail.\r\n'; + + 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; } + // Setup mail server var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { rejectUnauthorized: false } }; if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; } @@ -33,7 +41,7 @@ module.exports.CreateMeshMain = function (parent) { obj.smtpServer = nodemailer.createTransport(options); // Perform all e-mail substitution - function mailReplacements(text, domain, username, email, cookie) { + function mailReplacements(text, domain, username, email, options) { var url; if (domain.dns == null) { // Default domain or subdomain of the default. @@ -42,7 +50,10 @@ module.exports.CreateMeshMain = function (parent) { // Domain with a DNS name. url = 'http' + ((obj.parent.args.notls == null) ? 's' : '') + '://' + domain.dns + ':' + obj.parent.args.port + domain.url; } - if (cookie != null) { text = text.split('[[[CALLBACKURL]]]').join(url + 'checkmail?c=' + cookie) } + if (options) { + if (options.cookie != null) { text = text.split('[[[CALLBACKURL]]]').join(url + 'checkmail?c=' + options.cookie) } + if (options.meshid != null) { text = text.split('[[[CALLBACKURL]]]').join(url + 'meshagents?id=3&meshid=' + options.meshid.split('/')[2] + '&tag=mailto:' + EscapeHtml(email)) } + } return text.split('[[[USERNAME]]]').join(username).split('[[[SERVERURL]]]').join(url).split('[[[SERVERNAME]]]').join(domain.title); } @@ -54,17 +65,24 @@ module.exports.CreateMeshMain = function (parent) { // Send account check mail obj.sendAccountCheckMail = function (domain, username, email) { - if ((parent.certificates == null) || (parent.certificates.CommonName == null)) return; // If the server name is not set, no reset possible. + if ((parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName == 'un-configured')) return; // If the server name is not set, no reset possible. var cookie = obj.parent.encodeCookie({ u: domain.id + '/' + username, e: email, a: 1 }, obj.mailCookieEncryptionKey); - obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(accountCheckSubject, domain, username, email), text: mailReplacements(accountCheckMailText, domain, username, email, cookie), html: mailReplacements(accountCheckMailHtml, domain, username, email, cookie) }); + obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(accountCheckSubject, domain, username, email), text: mailReplacements(accountCheckMailText, domain, username, email, { cookie: cookie }), html: mailReplacements(accountCheckMailHtml, domain, username, email, { cookie: cookie }) }); sendNextMail(); } // Send account reset mail obj.sendAccountResetMail = function (domain, username, email) { - if ((parent.certificates == null) || (parent.certificates.CommonName == null)) return; // If the server name is not set, don't validate the email address. + if ((parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName == 'un-configured')) return; // If the server name is not set, don't validate the email address. var cookie = obj.parent.encodeCookie({ u: domain.id + '/' + username, e: email, a: 2 }, obj.mailCookieEncryptionKey); - obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(accountResetSubject, domain, username, email), text: mailReplacements(accountResetMailText, domain, username, email, cookie), html: mailReplacements(accountResetMailHtml, domain, username, email, cookie) }); + obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(accountResetSubject, domain, username, email), text: mailReplacements(accountResetMailText, domain, username, email, { cookie: cookie }), html: mailReplacements(accountResetMailHtml, domain, username, email, { cookie: cookie }) }); + sendNextMail(); + } + + // Send agent invite mail + obj.sendAgentInviteMail = function (domain, username, email, meshid) { + if ((parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName == 'un-configured')) return; // If the server name is not set, can't do this. + obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(accountInviteSubject, domain, username, email), text: mailReplacements(accountInviteMailText, domain, username, email, { meshid: meshid }), html: mailReplacements(accountInviteMailHtml, domain, username, email, { meshid: meshid }) }); sendNextMail(); } diff --git a/meshuser.js b/meshuser.js index 45052214..d7e9c93a 100644 --- a/meshuser.js +++ b/meshuser.js @@ -427,7 +427,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if ((command.meshtype == 1) || (command.meshtype == 2)) { // Create a type 1 agent-less Intel AMT mesh. obj.parent.crypto.randomBytes(48, function (err, buf) { - var meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');; + var meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); var links = {} links[user._id] = { name: user.name, rights: 0xFFFFFFFF }; var mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links }; @@ -876,6 +876,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { }); break; } + case 'inviteAgent': + { + if (obj.parent.parent.mailserver == null) return; // This operation requires the email server + if ((obj.parent.parent.certificates.CommonName == null) || (obj.parent.parent.certificates.CommonName == 'un-configured')) return; // Server name must be configured + if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain + + // Get the mesh + var mesh = obj.parent.meshes[command.meshid]; + if (mesh) { + if (mesh.mtype != 2) return; // This operation is only allowed for mesh type 2, agent mesh + + // Check if this user has rights to do this + //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return; + + // Perform email invitation + obj.parent.parent.mailserver.sendAgentInviteMail(domain, user.name, command.email, command.meshid); + } + break; + } } }); diff --git a/package.json b/package.json index 9d12f037..cde1ebe8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.2-s", + "version": "0.1.3-c", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index e453c9d2..7c7d9d3b 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1283,9 +1283,10 @@ } function ondockeydown(e) { - if (!xxdialogMode && xxcurrentView == 11 && desktop) return desktop.m.handleKeyDown(e); - if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyDown(e); + if (!xxdialogMode && xxcurrentView == 11 && desktop) { return desktop.m.handleKeyDown(e); } + if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) { return terminal.m.TermHandleKeyDown(e); } if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { haltEvent(e); return false; } // F5 Refresh on files + if (!xxdialogMode && xxcurrentView == 15) { return agentConsoleHandleKeys(e); } if (xxdialogMode || xxcurrentView != 1 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return; var processed = 0; if (Q('viewselect').value < 3) { @@ -1460,6 +1461,9 @@ } if (mesh.mtype == 2) { r += ' Add Agent'; + if (features & 64) { + r += ' Invite'; + } } return r; } @@ -1568,6 +1572,33 @@ QV('dlgAddCira2', val == 2); } + // Return true is the input string looks like an email address + function checkEmail(str) { + var x = str.split('@'); + var ok = ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)); + if (ok == true) { var y = x[1].split('.'); for (var i in y) { if (y[i].length == 0) { ok = false; } } } + return ok; + } + + function inviteAgentToMesh(meshid) { + if (xxdialogMode) return; + var mesh = meshes[meshid]; + var meshidx = meshid.substring(5); + if (meshidx[0] == '/') meshidx = meshidx.substring(1); + var x = "Invite someone to install the mesh agent. An email with be sent with the link to the mesh agent installation for " + EscapeHtml(mesh.name) + ".

"; + x += addHtmlValue('E-Mail', ''); + setDialogMode(2, "Invite Mesh Agent", 3, performAgentInvite, x, meshid); + validateAgentInvite(); + } + + function validateAgentInvite() { + QE('idx_dlgOkButton', checkEmail(Q('agentInviteEmail').value)); + } + + function performAgentInvite(button, meshid) { + meshserver.Send({ action: 'inviteAgent', meshid: meshid, email: Q('agentInviteEmail').value }); + } + function addAgentToMesh(meshid) { if (xxdialogMode) return; var mesh = meshes[meshid]; @@ -1580,7 +1611,7 @@ //x += "
To add a new computer to mesh " + EscapeHtml(mesh.name) + ", download the mesh agent and configuration file and install the agent on the computer to manage.

"; x += "
To add a new computer to mesh " + EscapeHtml(mesh.name) + ", download the mesh agent and install it the computer to manage. This agent has server and mesh information embedded within it.

"; x += addHtmlValue('Mesh Agent', 'Windows executable (.exe)'); - //x += addHtmlValue('Settings File', '' + EscapeHtml(mesh.name) + ' settings (.msh)'); + if (debugmode == true) { x += addHtmlValue('Settings File', '' + EscapeHtml(mesh.name) + ' settings (.msh)'); } x += "
"; // Linux agent install @@ -2491,7 +2522,9 @@ // Attribute: Mesh Agent Tag if ((node.agent != null) && (node.agent.tag != null)) { - x += addDeviceAttribute('Agent Tag', node.agent.tag); + var tag = EscapeHtml(node.agent.tag); + if (tag.startsWith('mailto:')) { tag = '' + tag.substring(7) + ''; } + x += addDeviceAttribute('Agent Tag', tag); } // Attribute: Intel AMT @@ -3664,7 +3697,6 @@ if (e.keyCode == 13 && consoleFocus == 0) { p15consoleSend(e); processed = 1; } else if (e.keyCode == 8 && consoleFocus == 0) { var x = box.value; box.value = x.substring(0, x.length - 1); processed = 1; } else if (e.keyCode == 27) { box.value = ''; processed = 1; } - else if (e.key.length === 1 && consoleFocus == 0) { box.value = ((box.value + e.key)); processed = 1; } else if ((e.keyCode == 38) || (e.keyCode == 40)) { // Arrow up || Arrow down var hindex = consoleHistory.indexOf(box.value); //console.log(hindex, consoleHistory); @@ -3673,12 +3705,27 @@ else if ((e.keyCode == 40) && (hindex == 0)) { box.value = ''; } processed = 1; } + else if (e.key.length === 1) { + //box.value = ((box.value + e.key)); + insertTextAtCursor(box, e.key); + processed = 1; + } } else { if (e.charCode != 0 && consoleFocus == 0) { box.value = ((box.value + String.fromCharCode(e.charCode))); processed = 1; } } if (processed > 0) { return haltEvent(e); } } + // Insert text at the cursor location on the + function insertTextAtCursor(ctrl, val) { + if (document.selection) { ctrl.focus(); sel = document.selection.createRange(); sel.text = val; } + else if (ctrl.selectionStart || ctrl.selectionStart == '0') { + var start = ctrl.selectionStart, end = ctrl.selectionEnd; + ctrl.value = ctrl.value.substring(0, start) + val + ctrl.value.substring(end, ctrl.value.length); + ctrl.setSelectionRange(end + 1, end + 1); + } else { ctrl.value += myValue; } + } + var consoleNode; function setupConsole() { // Setup the console diff --git a/webserver.js b/webserver.js index 5d6f7223..967b772f 100644 --- a/webserver.js +++ b/webserver.js @@ -675,6 +675,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate user = obj.users[req.session.userid] logoutcontrol = 'Welcome ' + user.name + '.'; } + + // Give the web page a list of supported server features var features = 0; if (obj.args.wanonly == true) { features += 1; } // WAN-only mode if (obj.args.lanonly == true) { features += 2; } // LAN-only mode @@ -682,6 +684,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if (domain.userQuota == -1) { features += 8; } // No server files mode if (obj.args.tlsoffload == true) { features += 16; } // No mutual-auth CIRA if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe + if ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName != 'un-configured')) { features += 64; } // Email invites + + // Send the master web application if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' Logout'; } // If a default user is in use or no user mode, don't display the logout button res.render(obj.path.join(__dirname, 'views/default'), { viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: args.port, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, mpspass: args.mpspass, webcerthash: obj.webCertificateHashBase64, footer: (domain.footer == null) ? '' : domain.footer }); } else { @@ -1426,7 +1431,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate if (xdomain != '') xdomain += "/"; var meshsettings = "MeshName=" + mesh.name + "\r\nMeshType=" + mesh.mtype + "\r\nMeshID=0x" + meshidhex + "\r\nServerID=" + serveridhex + "\r\n"; if (obj.args.lanonly != true) { meshsettings += "MeshServer=ws" + (obj.args.notls ? '' : 's') + "://" + getWebServerName(domain) + ":" + obj.args.port + "/" + xdomain + "agent.ashx\r\n"; } else { meshsettings += "MeshServer=local"; } - + if (req.query.tag != true) { meshsettings += "Tag=" + req.query.tag + "\r\n"; } res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=' + argentInfo.rname }); obj.parent.exeHandler.streamExeWithMeshPolicy({ platform: 'win32', sourceFileName: obj.parent.meshAgentBinaries[req.query.id].path, destinationStream: res, msh: meshsettings, peinfo: obj.parent.meshAgentBinaries[req.query.id].pe }); });