diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index 2a8fb39a..c3df4ea3 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -28,6 +28,7 @@ + diff --git a/agents/MeshService.exe b/agents/MeshService.exe index 89a96c5c..59731f1b 100644 Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ diff --git a/agents/MeshService64.exe b/agents/MeshService64.exe index 5a7340f5..1b347d2a 100644 Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ diff --git a/agents/meshagent_pi b/agents/meshagent_pi index 76df96d4..7c6a605d 100644 Binary files a/agents/meshagent_pi and b/agents/meshagent_pi differ diff --git a/agents/meshcmd.js b/agents/meshcmd.js index 3f4a0d4f..f758f422 100644 --- a/agents/meshcmd.js +++ b/agents/meshcmd.js @@ -279,12 +279,12 @@ function startLms() { //console.log("WebSocket for " + req.url.split('?')[0]); switch (req.url.split('?')[0]) { case '/lms.ashx': // MeshCommander control channel (PTHI) - socket.upgradeWebSocket(); - socket.on('data', processLmsControlData); + socket.ws = socket.upgradeWebSocket(); + socket.ws.on('data', processLmsControlData); break; case '/webrelay.ashx': // MeshCommander data channel (LME) - socket.upgradeWebSocket(); - amtLms.bindDuplexStream(socket, 'IPv4', 16992); + socket.ws = socket.upgradeWebSocket(); + amtLms.bindDuplexStream(socket.ws, 'IPv4', 16992); break; default: socket.end(); @@ -295,9 +295,8 @@ function startLms() { //console.log("WebRequest for " + req.url.split('?')[0]); switch (req.url.split('?')[0]) { case '/': // Serve MeshCommander Web Application for LMS - rsp.writeHead(200, 'OK', { Server: 'JSLMS', 'Cache-Control': 'max-age=0, no-cache', 'X-Frame-Options': 'DENY', 'Content-Type': 'text/html', 'Content-Encoding': 'gzip', ETag: _IntelAmtWebApp_etag }); - rsp.write(Buffer.from(_IntelAmtWebApp, 'base64')); - rsp.end(); + rsp.writeHead(200, 'OK', { Server: 'JSLMS', 'Cache-Control': 'max-age=0, no-cache', 'X-Frame-Options': 'DENY', 'Content-Type': 'text/html', 'Content-Encoding': 'gzip', 'Transfer-Encoding': 'chunked', ETag: _IntelAmtWebApp_etag }); + rsp.end(Buffer.from(_IntelAmtWebApp, 'base64')); break; default: // Unknown request rsp.statusCode = 404; diff --git a/agents/meshcore.js b/agents/meshcore.js index f438f7bf..14b319cb 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -692,7 +692,7 @@ function createMeshCore(agent) { // Called when receiving control data on websocket function onTunnelControlData(data) { if (typeof data != 'string') return; - //sendConsoleText('onTunnelControlData: ' + data); + sendConsoleText('onTunnelControlData: ' + data); //console.log('onTunnelControlData: ' + data); var obj; @@ -729,7 +729,7 @@ function createMeshCore(agent) { this.webrtc.on('connected', function () { sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected'); }); this.webrtc.on('disconnected', function () { sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected'); }); this.webrtc.on('dataChannel', function (rtcchannel) { - //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); + sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); rtcchannel.xrtc = this; this.rtcchannel = rtcchannel; this.rtcchannel.on('data', onTunnelWebRTCControlData); diff --git a/exeHandler.js b/exeHandler.js new file mode 100644 index 00000000..23274d87 --- /dev/null +++ b/exeHandler.js @@ -0,0 +1,308 @@ +/* +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. +*/ + +const exeJavaScriptGuid = 'B996015880544A19B7F7E9BE44914C18'; +const exeMeshPolicyGuid = 'B996015880544A19B7F7E9BE44914C19'; + + +// Changes a Windows Executable to add JavaScript inside of it. +// This method will write to destination stream and close it. +// +// options = { +// platform: 'win32' or 'linux', +// sourceFileName: 'pathToBinary', +// destinationStream: 'outputStream' +// js: 'jsContent', +// peinfo {} // Optional, if PE header already parsed place it here. +// } +// +module.exports.streamExeWithJavaScript = function (options) { + // Check all inputs + if (!options.platform) { throw ('platform not specified'); } + if (!options.destinationStream) { throw ('destination stream was not specified'); } + if (!options.sourceFileName) { throw ('source file not specified'); } + if (!options.js) { throw ('js content not specified'); } + + // If a Windows binary, parse it if not already parsed + if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = require('PE_Parser')(options.sourcePath); } + + // If unsigned Windows or Linux, we merge at the end with the GUID and no padding. + if ((options.platform == 'win32' && options.peinfo.CertificateTableAddress == 0) || options.platform != 'win32') { + // This is not a signed binary, so we can just send over the EXE then the MSH + options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' }); + options.destinationStream.sourceStream.options = options; + options.destinationStream.sourceStream.on('end', function () { + // Once the binary is streamed, write the msh + length + guid in that order. + this.options.destinationStream.write(this.options.js); // JS content + var sz = Buffer.alloc(4); + sz.writeUInt32BE(this.options.js.length, 0); + this.options.destinationStream.write(sz); // Length in small endian + this.options.destinationStream.end(Buffer.from(exeJavaScriptGuid, 'hex')); // GUID + }); + // Pipe the entire source binary without ending the stream. + options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false }); + } else { + throw ('js content not specified'); + } +} + + +// Changes a Windows Executable to add the MSH inside of it. +// This method will write to destination stream and close it. +// +// options = { +// platform: 'win32' or 'linux', +// sourceFileName: 'pathToBinary', +// destinationStream: 'outputStream' +// msh: 'mshContent', +// peinfo {} // Optional, if PE header already parsed place it here. +// } +// +module.exports.streamExeWithMeshPolicy = function (options) { + // Check all inputs + if (!options.platform) { throw ('platform not specified'); } + if (!options.destinationStream) { throw ('destination stream was not specified'); } + if (!options.sourceFileName) { throw ('source file not specified'); } + if (!options.msh) { throw ('msh content not specified'); } + + // If a Windows binary, parse it if not already parsed + if ((options.platform == 'win32') && (!options.peinfo)) { options.peinfo = require('PE_Parser')(options.sourcePath); } + + // If unsigned Windows or Linux, we merge at the end with the GUID and no padding. + if ((options.platform == 'win32' && options.peinfo.CertificateTableAddress == 0) || options.platform != 'win32') { + // This is not a signed binary, so we can just send over the EXE then the MSH + options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r' }); + options.destinationStream.sourceStream.options = options; + options.destinationStream.sourceStream.on('end', function () { + // Once the binary is streamed, write the msh + length + guid in that order. + this.options.destinationStream.write(this.options.msh); // MSH + var sz = Buffer.alloc(4); + sz.writeUInt32BE(this.options.msh.length, 0); + this.options.destinationStream.write(sz); // Length in small endian + this.options.destinationStream.end(Buffer.from(exeMeshPolicyGuid, 'hex')); // Guid + }); + // Pipe the entire source binary without ending the stream. + options.destinationStream.sourceStream.pipe(options.destinationStream, { end: false }); + } else if (options.platform == 'win32' && options.peinfo.CertificateTableAddress != 0) { + // This is a signed windows binary, so we need to do some magic + options.mshPadding = (8 - ((options.peinfo.certificateDwLength + options.msh.length + 20) % 8)) % 8; // Compute the padding with quad-align + + //console.log('old table size = ' + options.peinfo.CertificateTableSize); + options.peinfo.CertificateTableSize += (options.msh.length + 20 + options.mshPadding); // Add to the certificate table size + //console.log('new table size = ' + options.peinfo.CertificateTableSize); + //console.log('old certificate dwLength = ' + options.peinfo.certificateDwLength); + options.peinfo.certificateDwLength += (options.msh.length + 20 + options.mshPadding); // Add to the certificate size + //console.log('new certificate dwLength = ' + options.peinfo.certificateDwLength); + //console.log('values were padded with ' + options.mshPadding + ' bytes'); + + // Read up to the certificate table size and stream that out + options.destinationStream.sourceStream = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: 0, end: options.peinfo.CertificateTableSizePos - 1 }); + options.destinationStream.sourceStream.options = options; + options.destinationStream.sourceStream.on('end', function () { + // We sent up to the CertificateTableSize, now we need to send the updated certificate table size + //console.log('read first block'); + var sz = Buffer.alloc(4); + sz.writeUInt32LE(this.options.peinfo.CertificateTableSize, 0); + this.options.destinationStream.write(sz); // New cert table size + + // Stream everything up to the start of the certificate table entry + var source2 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableSizePos + 4, end: this.options.peinfo.CertificateTableAddress - 1 }); + source2.options = this.options; + source2.on('end', function () { + // We've sent up to the Certificate DWLength, which we need to update + //console.log('read second block'); + var sz = Buffer.alloc(4); + sz.writeUInt32LE(this.options.peinfo.certificateDwLength, 0); + this.options.destinationStream.write(sz); // New certificate length + + // Stream the entire binary until the end + var source3 = require('fs').createReadStream(options.sourceFileName, { flags: 'r', start: this.options.peinfo.CertificateTableAddress + 4 }); + source3.options = this.options; + source3.on('end', function () { + // We've sent the entire binary... Now send: Padding + MSH + MSHLength + GUID + //console.log('read third block'); + if (this.options.mshPadding > 0) { this.options.destinationStream.write(Buffer.alloc(this.options.mshPadding)); } // Padding + + this.options.destinationStream.write(this.options.msh); // MSH content + var sz = Buffer.alloc(4); + sz.writeUInt32BE(this.options.msh.length, 0); + this.options.destinationStream.write(sz); // MSH Length, small-endian + this.options.destinationStream.end(Buffer.from(exeMeshPolicyGuid, 'hex')); // MSH GUID + }); + source3.pipe(this.options.destinationStream, { end: false }); + this.options.sourceStream = source3; + }); + source2.pipe(this.options.destinationStream, { end: false }); + this.options.destinationStream.sourceStream = source2; + }); + this.options.destinationStream.sourceStream.pipe(this.options.destinationStream, { end: false }); + } +} + + +// Return information about this executable +// This works only on Windows binaries +module.exports.parseWindowsExecutable = function (exePath) { + var retVal = {}; + var fs = require('fs'); + var fd = fs.openSync(exePath, 'r'); + var bytesRead; + var dosHeader = Buffer.alloc(64); + var ntHeader = Buffer.alloc(24); + var optHeader; + + // Read the DOS header + bytesRead = fs.readSync(fd, dosHeader, 0, 64, 0); + if (dosHeader.readUInt16LE(0).toString(16).toUpperCase() != '5A4D') { throw ('unrecognized binary format'); } + + // Read the NT header + bytesRead = fs.readSync(fd, ntHeader, 0, ntHeader.length, dosHeader.readUInt32LE(60)); + if (ntHeader.slice(0, 4).toString('hex') != '50450000') { + throw ('not a PE file'); + } + switch (ntHeader.readUInt16LE(4).toString(16)) { + case '14c': // 32 bit + retVal.format = 'x86'; + break; + case '8664': // 64 bit + retVal.format = 'x64'; + break; + default: // Unknown + retVal.format = undefined; + break; + } + + retVal.optionalHeaderSize = ntHeader.readUInt16LE(20); + retVal.optionalHeaderSizeAddress = dosHeader.readUInt32LE(60) + 20; + + // Read the optional header + optHeader = Buffer.alloc(ntHeader.readUInt16LE(20)); + bytesRead = fs.readSync(fd, optHeader, 0, optHeader.length, dosHeader.readUInt32LE(60) + 24); + var numRVA = undefined; + + retVal.CheckSumPos = dosHeader.readUInt32LE(60) + 24 + 64; + retVal.SizeOfCode = optHeader.readUInt32LE(4); + retVal.SizeOfInitializedData = optHeader.readUInt32LE(8); + retVal.SizeOfUnInitializedData = optHeader.readUInt32LE(12); + + switch (optHeader.readUInt16LE(0).toString(16).toUpperCase()) { + case '10B': // 32 bit binary + numRVA = optHeader.readUInt32LE(92); + retVal.CertificateTableAddress = optHeader.readUInt32LE(128); + retVal.CertificateTableSize = optHeader.readUInt32LE(132); + retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 132; + retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 96; + break; + case '20B': // 64 bit binary + numRVA = optHeader.readUInt32LE(108); + retVal.CertificateTableAddress = optHeader.readUInt32LE(144); + retVal.CertificateTableSize = optHeader.readUInt32LE(148); + retVal.CertificateTableSizePos = dosHeader.readUInt32LE(60) + 24 + 148; + retVal.rvaStartAddress = dosHeader.readUInt32LE(60) + 24 + 112; + break; + default: + throw ('Unknown Value found for Optional Magic: ' + ntHeader.readUInt16LE(24).toString(16).toUpperCase()); + } + retVal.rvaCount = numRVA; + + if (retVal.CertificateTableAddress) { + // Read the authenticode certificate, only one cert (only the first entry) + var hdr = Buffer.alloc(8); + fs.readSync(fd, hdr, 0, hdr.length, retVal.CertificateTableAddress); + retVal.certificate = Buffer.alloc(hdr.readUInt32LE(0)); + fs.readSync(fd, retVal.certificate, 0, retVal.certificate.length, retVal.CertificateTableAddress + hdr.length); + retVal.certificate = retVal.certificate.toString('base64'); + retVal.certificateDwLength = hdr.readUInt32LE(0); + } + fs.closeSync(fd); + return (retVal); +} + + +// +// Hash a executable file. Works on both Windows and Linux. +// On Windows, will hash so that signature or .msh addition will not change the hash. Adding a .js on un-signed executable will change the hash. +// +// options = { +// sourcePath: Executable Path +// targetStream: Hashing Stream +// platform: Optional. Same value as process.platform ('win32' | 'linux' | 'darwin') +// } +// +module.exports.hashExecutableFile = function (options) { + if (!options.sourcePath || !options.targetStream) { throw ('Please specify sourcePath and targetStream'); } + var fs = require('fs'); + + // If not specified, try to determine platform type + if (!options.platform) { + try { + // If we can parse the executable, we know it's windows. + options.peinfo = module.exports.parseWindowsExecutable(options.sourcePath); + options.platform = 'win32'; + } catch (e) { + options.platform = 'other'; + } + } + + // Setup initial state + options.state = { endIndex: 0, checkSumIndex: 0, tableIndex: 0, stats: fs.statSync(options.sourcePath) }; + + if (options.platform == 'win32') + { + if (options.peinfo.CertificateTableAddress != 0) { options.state.endIndex = options.peinfo.CertificateTableAddress; } + options.state.tableIndex = options.peinfo.CertificateTableSizePos - 4; + options.state.checkSumIndex = options.peinfo.CheckSumPos; + } + + if (options.state.endIndex == 0) { + // We just need to check for Embedded MSH file + var fd = fs.openSync(options.sourcePath, 'r'); + var guid = Buffer.alloc(16); + var bytesRead; + + bytesRead = fs.readSync(fd, guid, 0, guid.length, options.state.stats.size - 16); + if (guid.toString('hex') == exeMeshPolicyGuid) { + bytesRead = fs.readSync(fd, guid, 0, 4, options.state.stats.size - 20); + options.state.endIndex = options.state.stats.size - 20 - guid.readUInt32LE(0); + } else { + options.state.endIndex = options.state.stats.size; + } + fs.closeSync(fd); + } + + // Linux does not have a checksum + if (options.state.checkSumIndex != 0) { + // Windows + options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.checkSumIndex - 1 }); + options.state.source.on('end', function () { + options.targetStream.write(Buffer.alloc(4)); + var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.checkSumIndex + 4, end: options.state.tableIndex - 1 }); + source.on('end', function () { + options.targetStream.write(Buffer.alloc(8)); + var source = fs.createReadStream(options.sourcePath, { flags: 'r', start: options.state.tableIndex + 8, end: options.state.endIndex - 1 }); + options.state.source = source; + options.state.source.pipe(options.targetStream); + }); + options.state.source = source; + options.state.source.pipe(options.targetStream, { end: false }); + }); + options.state.source.pipe(options.targetStream, { end: false }); + } else { + // Linux + options.state.source = fs.createReadStream(options.sourcePath, { flags: 'r', start: 0, end: options.state.endIndex - 1 }); + options.state.source.pipe(options.targetStream); + } +} diff --git a/meshcentral.js b/meshcentral.js index 6cf5d910..589c4b79 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -25,6 +25,7 @@ function CreateMeshCentralServer() { obj.fs = require('fs'); obj.path = require('path'); obj.crypto = require('crypto'); + obj.exeHandler = require('./exeHandler.js'); obj.platform = require('os').platform(); obj.args = require('minimist')(process.argv.slice(2)); obj.common = require('./common.js'); @@ -854,64 +855,65 @@ function CreateMeshCentralServer() { // List of possible mesh agents obj.meshAgentsArchitectureNumbers = { - 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true }, - 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole.exe', desc: 'Windows x86-32 console', update: true, amt: true }, - 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole.exe', desc: 'Windows x86-64 console', update: true, amt: true }, - 3: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true }, - 4: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true }, - 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true }, - 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true }, - 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false }, - 8: { id: 8, localname: 'MeshAgent-Linux-XEN-x86-32', rname: 'meshagent', desc: 'XEN x86-64', update: true, amt: false }, - 9: { id: 9, localname: 'meshagent_arm', rname: 'meshagent', desc: 'Linux ARM5', update: true, amt: false }, - 10: { id: 10, localname: 'MeshAgent-Linux-ARM-PlugPC', rname: 'meshagent', desc: 'Linux ARM PlugPC', update: true, amt: false }, - 11: { id: 11, localname: 'MeshAgent-OSX-x86-32', rname: 'meshosx', desc: 'Apple OSX x86-32', update: true, amt: false }, - 12: { id: 12, localname: 'MeshAgent-Android-x86', rname: 'meshandroid', desc: 'Android x86-32', update: true, amt: false }, - 13: { id: 13, localname: 'meshagent_pogo', rname: 'meshagent', desc: 'Linux ARM PogoPlug', update: true, amt: false }, - 14: { id: 14, localname: 'MeshAgent-Android-APK', rname: 'meshandroid', desc: 'Android Market', update: false, amt: false }, // Get this one from Google Play - 15: { id: 15, localname: 'meshagent_poky', rname: 'meshagent', desc: 'Linux Poky x86-32', update: true, amt: false }, - 16: { id: 16, localname: 'MeshAgent-OSX-x86-64', rname: 'meshagent', desc: 'Apple OSX x86-64', update: true, amt: false }, - 17: { id: 17, localname: 'MeshAgent-ChromeOS', rname: 'meshagent', desc: 'Google ChromeOS', update: false, amt: false }, // Get this one from Chrome store - 18: { id: 18, localname: 'meshagent_poky64', rname: 'meshagent', desc: 'Linux Poky x86-64', update: true, amt: false }, - 19: { id: 19, localname: 'meshagent_x86_nokvm', rname: 'meshagent', desc: 'Linux x86-32 NoKVM', update: true, amt: true }, - 20: { id: 20, localname: 'meshagent_x86-64_nokvm', rname: 'meshagent', desc: 'Linux x86-64 NoKVM', update: true, amt: true }, - 21: { id: 21, localname: 'MeshAgent-WinMinCore-Console-x86-32.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Console x86-32', update: true, amt: false }, - 22: { id: 22, localname: 'MeshAgent-WinMinCore-Service-x86-64.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Service x86-32', update: true, amt: false }, - 23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false }, // Get this one from NPM - 24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false }, - 25: { id: 25, localname: 'meshagent_pi', rname: 'meshagent', desc: 'Linux ARM - Raspberry Pi', update: true, amt: false } // "armv6l" and "armv7l" + 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown' }, + 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole.exe', desc: 'Windows x86-32 console', update: true, amt: true, platform: 'win32' }, + 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole.exe', desc: 'Windows x86-64 console', update: true, amt: true, platform: 'win32' }, + 3: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32' }, + 4: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32' }, + 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true, platform: 'linux' }, + 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true, platform: 'linux' }, + 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false, platform: 'linux' }, + 8: { id: 8, localname: 'MeshAgent-Linux-XEN-x86-32', rname: 'meshagent', desc: 'XEN x86-64', update: true, amt: false, platform: 'linux' }, + 9: { id: 9, localname: 'meshagent_arm', rname: 'meshagent', desc: 'Linux ARM5', update: true, amt: false, platform: 'linux' }, + 10: { id: 10, localname: 'MeshAgent-Linux-ARM-PlugPC', rname: 'meshagent', desc: 'Linux ARM PlugPC', update: true, amt: false, platform: 'linux' }, + 11: { id: 11, localname: 'MeshAgent-OSX-x86-32', rname: 'meshosx', desc: 'Apple OSX x86-32', update: true, amt: false, platform: 'linux' }, + 12: { id: 12, localname: 'MeshAgent-Android-x86', rname: 'meshandroid', desc: 'Android x86-32', update: true, amt: false, platform: 'linux' }, + 13: { id: 13, localname: 'meshagent_pogo', rname: 'meshagent', desc: 'Linux ARM PogoPlug', update: true, amt: false, platform: 'linux' }, + 14: { id: 14, localname: 'MeshAgent-Android-APK', rname: 'meshandroid', desc: 'Android Market', update: false, amt: false, platform: 'android' }, // Get this one from Google Play + 15: { id: 15, localname: 'meshagent_poky', rname: 'meshagent', desc: 'Linux Poky x86-32', update: true, amt: false, platform: 'linux' }, + 16: { id: 16, localname: 'MeshAgent-OSX-x86-64', rname: 'meshagent', desc: 'Apple OSX x86-64', update: true, amt: false, platform: 'osx' }, + 17: { id: 17, localname: 'MeshAgent-ChromeOS', rname: 'meshagent', desc: 'Google ChromeOS', update: false, amt: false, platform: 'chromeos' }, // Get this one from Chrome store + 18: { id: 18, localname: 'meshagent_poky64', rname: 'meshagent', desc: 'Linux Poky x86-64', update: true, amt: false, platform: 'linux' }, + 19: { id: 19, localname: 'meshagent_x86_nokvm', rname: 'meshagent', desc: 'Linux x86-32 NoKVM', update: true, amt: true, platform: 'linux' }, + 20: { id: 20, localname: 'meshagent_x86-64_nokvm', rname: 'meshagent', desc: 'Linux x86-64 NoKVM', update: true, amt: true, platform: 'linux' }, + 21: { id: 21, localname: 'MeshAgent-WinMinCore-Console-x86-32.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Console x86-32', update: true, amt: false, platform: 'win32' }, + 22: { id: 22, localname: 'MeshAgent-WinMinCore-Service-x86-64.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Service x86-32', update: true, amt: false, platform: 'win32' }, + 23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false, platform: 'node' }, // Get this one from NPM + 24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false, platform: 'linux' }, + 25: { id: 25, localname: 'meshagent_pi', rname: 'meshagent', desc: 'Linux ARM - Raspberry Pi', update: true, amt: false, platform: 'linux' } // "armv6l" and "armv7l" }; // Update the list of available mesh agents obj.updateMeshAgentsTable = function (func) { var archcount = 0; - for (var archid in obj.meshAgentsArchitectureNumbers) { archcount++; } for (var archid in obj.meshAgentsArchitectureNumbers) { var agentpath = obj.path.join(__dirname, 'agents', obj.meshAgentsArchitectureNumbers[archid].localname); - var stream = null; - try { - stream = obj.fs.createReadStream(agentpath); - stream.on('data', function (data) { this.hash.update(data, 'binary') }); - stream.on('error', function (data) { - // If there is an error reading this file, make sure this agent is not in the agent table - if (obj.meshAgentBinaries[this.info.id] != null) { delete obj.meshAgentBinaries[this.info.id]; } + + // Fetch all the agent binary information + obj.meshAgentBinaries[archid] = obj.common.Clone(obj.meshAgentsArchitectureNumbers[archid]); + obj.meshAgentBinaries[archid].path = agentpath; + obj.meshAgentBinaries[archid].url = ((obj.args.notls == true) ? 'http://' : 'https://') + obj.certificates.CommonName + ':' + obj.args.port + '/meshagents?id=' + archid; + var stats = null; + try { stats = obj.fs.statSync(agentpath) } catch (e) { } + if ((stats != null)) { + // If file exists + archcount++; + obj.meshAgentBinaries[archid].size = stats.size; + // If this is a windows binary, pull binary information + if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') { + try { obj.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (e) { } + } + // Hash the binary + var hashStream = obj.crypto.createHash('sha384'); + hashStream.archid = archid; + hashStream.on('data', function (data) { + obj.meshAgentBinaries[this.archid].hash = data.toString('hex'); if ((--archcount == 0) && (func != null)) { func(); } }); - stream.on('end', function () { - // Add the agent to the agent table with all information and the hash - obj.meshAgentBinaries[this.info.id] = obj.common.Clone(this.info); - obj.meshAgentBinaries[this.info.id].hash = this.hash.digest('hex'); - obj.meshAgentBinaries[this.info.id].path = this.agentpath; - obj.meshAgentBinaries[this.info.id].url = ((obj.args.notls == true) ? 'http://' : 'https://') + obj.certificates.CommonName + ':' + obj.args.port + '/meshagents?id=' + this.info.id; - var stats = null; - try { stats = obj.fs.statSync(this.agentpath) } catch (e) { } - if (stats != null) { obj.meshAgentBinaries[this.info.id].size = stats.size; } - if ((--archcount == 0) && (func != null)) { func(); } - }); - stream.info = obj.meshAgentsArchitectureNumbers[archid]; - stream.agentpath = agentpath; - stream.hash = obj.crypto.createHash('sha384', stream); - } catch (e) { if ((--archcount == 0) && (func != null)) { func(); } } + var options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform }; + if (obj.meshAgentBinaries[archid].pe != null) { options.peinfo = obj.meshAgentBinaries[archid].pe; } + obj.exeHandler.hashExecutableFile(options); + } } } diff --git a/package.json b/package.json index 12663b94..9d12f037 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.1.2-u", + "version": "0.1.2-s", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default.handlebars b/views/default.handlebars index 563ce697..e453c9d2 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1577,9 +1577,10 @@ x += addHtmlValue('Operating System', '') + '
'; // Windows agent install - 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 += addHtmlValue('Mesh Agent', 'Windows executable (.exe)'); - x += addHtmlValue('Settings File', '' + EscapeHtml(mesh.name) + ' settings (.msh)'); + //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)'); x += "
"; // Linux agent install diff --git a/webserver.js b/webserver.js index 9ec1b48c..5d6f7223 100644 --- a/webserver.js +++ b/webserver.js @@ -1397,12 +1397,40 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // Handle a request to download a mesh agent obj.handleMeshAgentRequest = function (req, res) { + var domain = checkUserIpAddress(req, res); + if (domain == null) return; if (req.query.id != null) { // Send a specific mesh agent back var argentInfo = obj.parent.meshAgentBinaries[req.query.id]; if (argentInfo == null) { res.sendStatus(404); return; } - 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 }); - res.sendFile(argentInfo.path); + if ((req.query.meshid == null) || (argentInfo.platform != 'win32')) { + 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 }); + res.sendFile(argentInfo.path); + } else { + // We are going to embed the .msh file into the Windows executable (signed or not). + // First, query the meshid to build the .msh file + obj.db.Get('mesh/' + domain.id + '/' + req.query.meshid, function (err, meshes) { + if (meshes.length != 1) { res.sendStatus(401); return; } + var mesh = meshes[0]; + + // Check if this user has rights to do this + //var user = obj.users[req.session.userid]; + //if ((user == null) || (mesh.links[user._id] == null) || ((mesh.links[user._id].rights & 1) == 0)) { res.sendStatus(401); return; } + //if (domain.id != mesh.domain) { res.sendStatus(401); return; } + + var meshidhex = new Buffer(req.query.meshid.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase(); + var serveridhex = new Buffer(obj.agentCertificateHashBase64.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex').toUpperCase(); + + // Build the agent connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly. + var xdomain = (domain.dns == null) ? domain.id : ''; + 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"; } + + 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 }); + }); + } } else if (req.query.script != null) { // Send a specific mesh install script back var scriptInfo = obj.parent.meshAgentInstallScripts[req.query.script]; @@ -1413,6 +1441,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate // Send meshcmd for a specific platform back var argentInfo = obj.parent.meshAgentBinaries[req.query.meshcmd]; if ((argentInfo == null) || (obj.parent.defaultMeshCmd == null)) { res.sendStatus(404); return; } + res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename=meshcmd' + ((req.query.meshcmd <= 4) ? '.exe' : '') }); + res.statusCode = 200; + obj.parent.exeHandler.streamExeWithJavaScript({ platform: argentInfo.platform, sourceFileName: argentInfo.path, destinationStream: res, js: new Buffer(obj.parent.defaultMeshCmd, 'utf8'), peinfo: argentInfo.pe }); + + /* // Load the agent obj.fs.readFile(argentInfo.path, function (err, agentexe) { if (err != null) { res.sendStatus(404); return; } @@ -1423,6 +1456,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate tail.writeInt32BE(agentexe.length + meshcmdbuf.length + 8, 4); res.send(Buffer.concat([agentexe, meshcmdbuf, tail])); }); + */ } else if (req.query.meshaction != null) { var domain = checkUserIpAddress(req, res); if (domain == null) { res.sendStatus(404); return; } @@ -1773,3 +1807,4 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate return obj; } +