var MemoryStream = require('MemoryStream'); var lme_id = 0; var APF_DISCONNECT = 1; var APF_SERVICE_REQUEST = 5; var APF_SERVICE_ACCEPT = 6; var APF_USERAUTH_REQUEST = 50; var APF_USERAUTH_FAILURE = 51; var APF_USERAUTH_SUCCESS = 52; var APF_GLOBAL_REQUEST = 80; var APF_REQUEST_SUCCESS = 81; var APF_REQUEST_FAILURE = 82; var APF_CHANNEL_OPEN = 90; var APF_CHANNEL_OPEN_CONFIRMATION = 91; var APF_CHANNEL_OPEN_FAILURE = 92; var APF_CHANNEL_WINDOW_ADJUST = 93; var APF_CHANNEL_DATA = 94; var APF_CHANNEL_CLOSE = 97; var APF_PROTOCOLVERSION = 192; function lme_object() { this.ourId = ++lme_id; this.amtId = -1; this.LME_CHANNEL_STATUS = 'LME_CS_FREE'; this.txWindow = 0; this.rxWindow = 0; this.localPort = 0; this.errorCount = 0; } function stream_bufferedWrite() { var emitterUtils = require('events').inherits(this); this.buffer = []; this._readCheckImmediate = undefined; // Writable Events emitterUtils.createEvent('close'); emitterUtils.createEvent('drain'); emitterUtils.createEvent('error'); emitterUtils.createEvent('finish'); emitterUtils.createEvent('pipe'); emitterUtils.createEvent('unpipe'); // Readable Events emitterUtils.createEvent('readable'); this.isEmpty = function () { return (this.buffer.length == 0); }; this.isWaiting = function () { return (this._readCheckImmediate == undefined); }; 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); chunk.copy(tmp); this.buffer.push({ offset: 0, data: tmp }); this.emit('readable'); return (this.buffer.length == 0 ? true : false); }; 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) { var len = this.buffer[0].data.length - this.buffer[0].offset; var offset = this.buffer[0].offset; 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 { // Reading the entire thing list.push(this.buffer[0].data.slice(offset)); bytesRead += len; this.buffer.shift(); } } this._readCheckImmediate = setImmediate(function (buffered) { buffered._readCheckImmediate = undefined; if(buffered.buffer.length == 0) { // drained buffered.emit('drain'); } else { // not drained buffered.emit('readable'); } }, this); return (Buffer.concat(list)); }; } 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.Parent.emit('error', e); }); this._LME.on('connect', function () { this.LMS.emit('connect'); this.on('data', function (chunk) { // this = HECI var cmd = chunk.readUInt8(0); switch(cmd) { default: //console.log('Received ' + chunk.length + ' bytes of data for LMS'); //console.log('Command = ' + cmd); break; 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') { var outBuffer = Buffer.alloc(5 + nameLen); outBuffer.writeUInt8(6, 0); outBuffer.writeUInt32BE(nameLen, 1); outBuffer.write(name.toString(), 5); this.write(outBuffer); //console.log('Answering APF_SERVICE_REQUEST'); } else { //console.log('UNKNOWN APF_SERVICE_REQUEST'); } break; case APF_GLOBAL_REQUEST: var nameLen = chunk.readUInt32BE(1); var name = chunk.slice(5, nameLen + 5).toString(); 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) { 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) { //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort); this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort); }); var outBuffer = Buffer.alloc(5); outBuffer.writeUInt8(81, 0); outBuffer.writeUInt32BE(port, 1); this.write(outBuffer); break; case 'cancel-tcpip-forward': break; case 'udp-send-to@amt.intel.com': break; default: //console.log("Unknown APF_GLOBAL_REQUEST for: " + name); 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: var major = chunk.readUInt32BE(1); var minor = chunk.readUInt32BE(5); var reason = chunk.readUInt32BE(9); var outBuffer = Buffer.alloc(93); outBuffer.writeUInt8(192, 0); outBuffer.writeUInt32BE(1, 1); outBuffer.writeUInt32BE(0, 5); outBuffer.writeUInt32BE(reason, 9); //console.log('Answering PROTOCOL_VERSION'); this.write(outBuffer); break; case APF_CHANNEL_WINDOW_ADJUST: var rChannelId = chunk.readUInt32BE(1); var bytesToAdd = chunk.readUInt32BE(5); if (this.sockets[rChannelId] != undefined) { this.sockets[rChannelId].lme.txWindow += bytesToAdd; if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting()) { this.sockets[rChannelId].bufferedStream.emit('readable'); } } else { //console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_WINDOW_ADJUST'); } break; case APF_CHANNEL_DATA: var rChannelId = chunk.readUInt32BE(1); var dataLen = chunk.readUInt32BE(5); var data = chunk.slice(9, 9 + dataLen); if (this.sockets[rChannelId] != undefined) { this.sockets[rChannelId].pendingBytes.push(data.length); this.sockets[rChannelId].write(data, function () { var written = this.pendingBytes.shift(); var outBuffer = Buffer.alloc(9); outBuffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0); outBuffer.writeUInt32BE(this.lme.amtId, 1); outBuffer.writeUInt32BE(written, 5); this.HECI.write(outBuffer); }); } 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(); 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 { //console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_CLOSE'); } break; } }); }); this.bindDuplexStream = function (duplexStream, remoteFamily, localPort) { var socket = duplexStream; //console.log('New [' + remoteFamily + '] Virtual Connection/' + socket.localPort); socket.pendingBytes = []; socket.HECI = this._LME; socket.LMS = this; socket.lme = new lme_object(); socket.lme.Socket = socket; var buffer = new MemoryStream(); buffer.writeUInt8(0x5A); buffer.writeUInt32BE(15); buffer.write('forwarded-tcpip'); buffer.writeUInt32BE(socket.lme.ourId); buffer.writeUInt32BE(this.INITIAL_RXWINDOW_SIZE); buffer.writeUInt32BE(0xFFFFFFFF); for (var i = 0; i < 2; ++i) { if (remoteFamily == 'IPv6') { buffer.writeUInt32BE(3); buffer.write('::1'); } else { buffer.writeUInt32BE(9); buffer.write('127.0.0.1'); } buffer.writeUInt32BE(localPort); } this._LME.write(buffer.buffer); if (this._LME.sockets == undefined) { this._LME.sockets = {}; } this._LME.sockets[socket.lme.ourId] = socket; socket.pause(); }; this._LME.connect(heci.GUIDS.LME, { noPipeline: 0 }); } module.exports = lme_heci;