MeshCentral/rdp/protocol/rdp.js
2022-06-23 22:10:07 -07:00

374 lines
12 KiB
JavaScript

/*
* Copyright (c) 2014-2015 Sylvain Peyrefitte
*
* This file is part of node-rdpjs.
*
* node-rdpjs is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var net = require('net');
var inherits = require('util').inherits;
var events = require('events');
var layer = require('../core').layer;
var error = require('../core').error;
var rle = require('../core').rle;
var log = require('../core').log;
var TPKT = require('./tpkt');
var x224 = require('./x224');
var t125 = require('./t125');
var pdu = require('./pdu');
/**
* decompress bitmap from RLE algorithm
* @param bitmap {object} bitmap object of bitmap event of node-rdpjs
*/
function decompress(bitmap) {
var fName = null;
switch (bitmap.bitsPerPixel.value) {
case 15:
fName = 'bitmap_decompress_15';
break;
case 16:
fName = 'bitmap_decompress_16';
break;
case 24:
fName = 'bitmap_decompress_24';
break;
case 32:
fName = 'bitmap_decompress_32';
break;
default:
throw 'invalid bitmap data format';
}
var input = new Uint8Array(bitmap.bitmapDataStream.value);
var inputPtr = rle._malloc(input.length);
var inputHeap = new Uint8Array(rle.HEAPU8.buffer, inputPtr, input.length);
inputHeap.set(input);
var ouputSize = bitmap.width.value * bitmap.height.value * 4;
var outputPtr = rle._malloc(ouputSize);
var outputHeap = new Uint8Array(rle.HEAPU8.buffer, outputPtr, ouputSize);
var res = rle.ccall(fName,
'number',
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
[outputHeap.byteOffset, bitmap.width.value, bitmap.height.value, bitmap.width.value, bitmap.height.value, inputHeap.byteOffset, input.length]
);
var output = new Uint8ClampedArray(outputHeap.buffer, outputHeap.byteOffset, ouputSize);
rle._free(inputPtr);
rle._free(outputPtr);
return output;
}
/**
* Main RDP module
*/
function RdpClient(config) {
config = config || {};
this.connected = false;
this.bufferLayer = new layer.BufferLayer(new net.Socket());
this.tpkt = new TPKT(this.bufferLayer);
this.x224 = new x224.Client(this.tpkt, config);
this.mcs = new t125.mcs.Client(this.x224);
this.sec = new pdu.sec.Client(this.mcs, this.tpkt);
this.cliprdr = new pdu.cliprdr.Client(this.mcs);
this.global = new pdu.global.Client(this.sec, this.sec);
// config log level
log.level = log.Levels[config.logLevel || 'INFO'] || log.Levels.INFO;
// credentials
if (config.domain) {
this.sec.infos.obj.domain.value = Buffer.from(config.domain + '\x00', 'ucs2');
}
if (config.userName) {
this.sec.infos.obj.userName.value = Buffer.from(config.userName + '\x00', 'ucs2');
}
if (config.password) {
this.sec.infos.obj.password.value = Buffer.from(config.password + '\x00', 'ucs2');
}
if (config.workingDir) {
this.sec.infos.obj.workingDir.value = Buffer.from(config.workingDir + '\x00', 'ucs2');
}
if (config.alternateShell) {
this.sec.infos.obj.alternateShell.value = Buffer.from(config.alternateShell + '\x00', 'ucs2');
}
if (config.perfFlags != null) {
this.sec.infos.obj.extendedInfo.obj.performanceFlags.value = config.perfFlags;
} else {
if (config.enablePerf) {
this.sec.infos.obj.extendedInfo.obj.performanceFlags.value =
pdu.sec.PerfFlag.PERF_DISABLE_WALLPAPER
| pdu.sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS
| pdu.sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW
| pdu.sec.PerfFlag.PERF_DISABLE_THEMING
| pdu.sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG;
}
}
if (config.autoLogin) {
this.sec.infos.obj.flag.value |= pdu.sec.InfoFlag.INFO_AUTOLOGON;
}
if (config.screen && config.screen.width && config.screen.height) {
this.mcs.clientCoreData.obj.desktopWidth.value = config.screen.width;
this.mcs.clientCoreData.obj.desktopHeight.value = config.screen.height;
}
log.debug('screen ' + this.mcs.clientCoreData.obj.desktopWidth.value + 'x' + this.mcs.clientCoreData.obj.desktopHeight.value);
// config keyboard layout
switch (config.locale) {
case 'fr':
log.debug('french keyboard layout');
this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.FRENCH;
break;
case 'en':
default:
log.debug('english keyboard layout');
this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.US;
}
this.cliprdr.on('clipboard', (content) => {
this.emit('clipboard', content)
});
//bind all events
var self = this;
this.global.on('connect', function () {
self.connected = true;
self.emit('connect');
}).on('session', function () {
self.emit('session');
}).on('close', function () {
self.connected = false;
self.emit('close');
}).on('pointer', function (cursorId, cursorStr) {
self.emit('pointer', cursorId, cursorStr);
}).on('bitmap', function (bitmaps) {
for (var bitmap in bitmaps) {
var bitmapData = bitmaps[bitmap].obj.bitmapDataStream.value;
var isCompress = bitmaps[bitmap].obj.flags.value & pdu.data.BitmapFlag.BITMAP_COMPRESSION;
if (isCompress && config.decompress) {
bitmapData = decompress(bitmaps[bitmap].obj);
isCompress = false;
}
self.emit('bitmap', {
destTop: bitmaps[bitmap].obj.destTop.value,
destLeft: bitmaps[bitmap].obj.destLeft.value,
destBottom: bitmaps[bitmap].obj.destBottom.value,
destRight: bitmaps[bitmap].obj.destRight.value,
width: bitmaps[bitmap].obj.width.value,
height: bitmaps[bitmap].obj.height.value,
bitsPerPixel: bitmaps[bitmap].obj.bitsPerPixel.value,
isCompress: isCompress,
data: bitmapData
});
}
}).on('error', function (err) {
log.warn(err.code + '(' + err.message + ')\n' + err.stack);
if (err instanceof error.FatalError) { throw err; } else { self.emit('error', err); }
});
}
inherits(RdpClient, events.EventEmitter);
/**
* Connect RDP client
* @param host {string} destination host
* @param port {integer} destination port
*/
RdpClient.prototype.connect = function (host, port) {
log.debug('connect to ' + host + ':' + port);
var self = this;
this.bufferLayer.socket.connect(port, host, function () {
// in client mode connection start from x224 layer
self.x224.connect();
});
return this;
};
/**
* Close RDP client
*/
RdpClient.prototype.close = function () {
if (this.connected) {
this.global.close();
}
this.connected = false;
return this;
};
/**
* Send pointer event to server
* @param x {integer} mouse x position
* @param y {integer} mouse y position
* @param button {integer} button number of mouse
* @param isPressed {boolean} state of button
*/
RdpClient.prototype.sendPointerEvent = function (x, y, button, isPressed) {
if (!this.connected)
return;
var event = pdu.data.pointerEvent();
if (isPressed) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN;
}
switch (button) {
case 1:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1;
break;
case 2:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2;
break;
case 3:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
break;
default:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE;
}
event.obj.xPos.value = x;
event.obj.yPos.value = y;
this.global.sendInputEvents([event]);
};
/**
* send scancode event
* @param code {integer}
* @param isPressed {boolean}
* @param extended {boolenan} extended keys
*/
RdpClient.prototype.sendKeyEventScancode = function (code, isPressed, extended) {
if (!this.connected)
return;
extended = extended || false;
var event = pdu.data.scancodeKeyEvent();
event.obj.keyCode.value = code;
if (!isPressed) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE;
}
if (extended) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED;
}
this.global.sendInputEvents([event]);
};
/**
* Send key event as unicode
* @param code {integer}
* @param isPressed {boolean}
*/
RdpClient.prototype.sendKeyEventUnicode = function (code, isPressed) {
if (!this.connected)
return;
var event = pdu.data.unicodeKeyEvent();
event.obj.unicode.value = code;
if (!isPressed) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE;
}
this.global.sendInputEvents([event]);
}
/**
* Wheel mouse event
* @param x {integer} mouse x position
* @param y {integer} mouse y position
* @param step {integer} wheel step
* @param isNegative {boolean}
* @param isHorizontal {boolean}
*/
RdpClient.prototype.sendWheelEvent = function (x, y, step, isNegative, isHorizontal) {
if (!this.connected)
return;
var event = pdu.data.pointerEvent();
if (isHorizontal) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL;
}
else {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL;
}
if (isNegative) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE;
}
event.obj.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask)
event.obj.xPos.value = x;
event.obj.yPos.value = y;
this.global.sendInputEvents([event]);
}
/**
* Clipboard event
* @param data {String} content for clipboard
*/
RdpClient.prototype.setClipboardData = function (content) {
this.cliprdr.setClipboardData(content);
}
function createClient(config) {
return new RdpClient(config);
};
/**
* RDP server side protocol
* @param config {object} configuration
* @param socket {net.Socket}
*/
function RdpServer(config, socket) {
if (!(config.key && config.cert)) {
throw new error.FatalError('NODE_RDP_PROTOCOL_RDP_SERVER_CONFIG_MISSING', 'missing cryptographic tools')
}
this.connected = false;
this.bufferLayer = new layer.BufferLayer(socket);
this.tpkt = new TPKT(this.bufferLayer);
this.x224 = new x224.Server(this.tpkt, config.key, config.cert);
this.mcs = new t125.mcs.Server(this.x224);
};
inherits(RdpServer, events.EventEmitter);
function createServer(config, next) {
return net.createServer(function (socket) {
next(new RdpServer(config, socket));
});
};
/**
* Module exports
*/
module.exports = {
createClient: createClient,
createServer: createServer
};