| 1 | /* |
| 2 | * Copyright (c) 2014-2015 Sylvain Peyrefitte |
| 3 | * |
| 4 | * This file is part of node-rdpjs. |
| 5 | * |
| 6 | * node-rdpjs is free software: you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation, either version 3 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | */ |
| 19 | |
| 20 | var net = require('net'); |
| 21 | var inherits = require('util').inherits; |
| 22 | var events = require('events'); |
| 23 | var layer = require('../core').layer; |
| 24 | var error = require('../core').error; |
| 25 | var rle = require('../core').rle; |
| 26 | var log = require('../core').log; |
| 27 | var TPKT = require('./tpkt'); |
| 28 | var x224 = require('./x224'); |
| 29 | var t125 = require('./t125'); |
| 30 | var pdu = require('./pdu'); |
| 31 | |
| 32 | /** |
| 33 | * decompress bitmap from RLE algorithm |
| 34 | * @param bitmap {object} bitmap object of bitmap event of node-rdpjs |
| 35 | */ |
| 36 | function decompress(bitmap) { |
| 37 | var fName = null; |
| 38 | switch (bitmap.bitsPerPixel.value) { |
| 39 | case 15: |
| 40 | fName = 'bitmap_decompress_15'; |
| 41 | break; |
| 42 | case 16: |
| 43 | fName = 'bitmap_decompress_16'; |
| 44 | break; |
| 45 | case 24: |
| 46 | fName = 'bitmap_decompress_24'; |
| 47 | break; |
| 48 | case 32: |
| 49 | fName = 'bitmap_decompress_32'; |
| 50 | break; |
| 51 | default: |
| 52 | throw 'invalid bitmap data format'; |
| 53 | } |
| 54 | |
| 55 | var input = new Uint8Array(bitmap.bitmapDataStream.value); |
| 56 | var inputPtr = rle._malloc(input.length); |
| 57 | var inputHeap = new Uint8Array(rle.HEAPU8.buffer, inputPtr, input.length); |
| 58 | inputHeap.set(input); |
| 59 | |
| 60 | var ouputSize = bitmap.width.value * bitmap.height.value * 4; |
| 61 | var outputPtr = rle._malloc(ouputSize); |
| 62 | |
| 63 | var outputHeap = new Uint8Array(rle.HEAPU8.buffer, outputPtr, ouputSize); |
| 64 | |
| 65 | var res = rle.ccall(fName, |
| 66 | 'number', |
| 67 | ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'], |
| 68 | [outputHeap.byteOffset, bitmap.width.value, bitmap.height.value, bitmap.width.value, bitmap.height.value, inputHeap.byteOffset, input.length] |
| 69 | ); |
| 70 | |
| 71 | var output = new Uint8ClampedArray(outputHeap.buffer, outputHeap.byteOffset, ouputSize); |
| 72 | |
| 73 | rle._free(inputPtr); |
| 74 | rle._free(outputPtr); |
| 75 | |
| 76 | return output; |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Main RDP module |
| 81 | */ |
| 82 | function RdpClient(config) { |
| 83 | config = config || {}; |
| 84 | this.connected = false; |
| 85 | this.bufferLayer = new layer.BufferLayer(new net.Socket()); |
| 86 | this.tpkt = new TPKT(this.bufferLayer); |
| 87 | this.x224 = new x224.Client(this.tpkt, config); |
| 88 | this.mcs = new t125.mcs.Client(this.x224); |
| 89 | this.sec = new pdu.sec.Client(this.mcs, this.tpkt); |
| 90 | this.cliprdr = new pdu.cliprdr.Client(this.mcs); |
| 91 | this.global = new pdu.global.Client(this.sec, this.sec); |
| 92 | |
| 93 | // config log level |
| 94 | log.level = log.Levels[config.logLevel || 'INFO'] || log.Levels.INFO; |
| 95 | |
| 96 | // credentials |
| 97 | if (config.domain) { |
| 98 | this.sec.infos.obj.domain.value = Buffer.from(config.domain + '\x00', 'ucs2'); |
| 99 | } |
| 100 | if (config.userName) { |
| 101 | this.sec.infos.obj.userName.value = Buffer.from(config.userName + '\x00', 'ucs2'); |
| 102 | } |
| 103 | if (config.password) { |
| 104 | this.sec.infos.obj.password.value = Buffer.from(config.password + '\x00', 'ucs2'); |
| 105 | } |
| 106 | if (config.workingDir) { |
| 107 | this.sec.infos.obj.workingDir.value = Buffer.from(config.workingDir + '\x00', 'ucs2'); |
| 108 | } |
| 109 | if (config.alternateShell) { |
| 110 | this.sec.infos.obj.alternateShell.value = Buffer.from(config.alternateShell + '\x00', 'ucs2'); |
| 111 | } |
| 112 | |
| 113 | if (config.perfFlags != null) { |
| 114 | this.sec.infos.obj.extendedInfo.obj.performanceFlags.value = config.perfFlags; |
| 115 | } else { |
| 116 | if (config.enablePerf) { |
| 117 | this.sec.infos.obj.extendedInfo.obj.performanceFlags.value = |
| 118 | pdu.sec.PerfFlag.PERF_DISABLE_WALLPAPER |
| 119 | | pdu.sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS |
| 120 | | pdu.sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW |
| 121 | | pdu.sec.PerfFlag.PERF_DISABLE_THEMING |
| 122 | | pdu.sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | if (config.autoLogin) { |
| 127 | this.sec.infos.obj.flag.value |= pdu.sec.InfoFlag.INFO_AUTOLOGON; |
| 128 | } |
| 129 | |
| 130 | if (config.screen && config.screen.width && config.screen.height) { |
| 131 | this.mcs.clientCoreData.obj.desktopWidth.value = config.screen.width; |
| 132 | this.mcs.clientCoreData.obj.desktopHeight.value = config.screen.height; |
| 133 | } |
| 134 | |
| 135 | log.debug('screen ' + this.mcs.clientCoreData.obj.desktopWidth.value + 'x' + this.mcs.clientCoreData.obj.desktopHeight.value); |
| 136 | |
| 137 | // config keyboard layout |
| 138 | switch (config.locale) { |
| 139 | case 'fr': |
| 140 | log.debug('french keyboard layout'); |
| 141 | this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.FRENCH; |
| 142 | break; |
| 143 | case 'en': |
| 144 | default: |
| 145 | log.debug('english keyboard layout'); |
| 146 | this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.US; |
| 147 | } |
| 148 | |
| 149 | this.cliprdr.on('clipboard', (content) => { |
| 150 | this.emit('clipboard', content) |
| 151 | }); |
| 152 | |
| 153 | //bind all events |
| 154 | var self = this; |
| 155 | this.global.on('connect', function () { |
| 156 | self.connected = true; |
| 157 | self.emit('connect'); |
| 158 | }).on('session', function () { |
| 159 | self.emit('session'); |
| 160 | }).on('close', function () { |
| 161 | self.connected = false; |
| 162 | self.emit('close'); |
| 163 | }).on('pointer', function (cursorId, cursorStr) { |
| 164 | self.emit('pointer', cursorId, cursorStr); |
| 165 | }).on('bitmap', function (bitmaps) { |
| 166 | for (var bitmap in bitmaps) { |
| 167 | var bitmapData = bitmaps[bitmap].obj.bitmapDataStream.value; |
| 168 | var isCompress = bitmaps[bitmap].obj.flags.value & pdu.data.BitmapFlag.BITMAP_COMPRESSION; |
| 169 | |
| 170 | if (isCompress && config.decompress) { |
| 171 | bitmapData = decompress(bitmaps[bitmap].obj); |
| 172 | isCompress = false; |
| 173 | } |
| 174 | |
| 175 | self.emit('bitmap', { |
| 176 | destTop: bitmaps[bitmap].obj.destTop.value, |
| 177 | destLeft: bitmaps[bitmap].obj.destLeft.value, |
| 178 | destBottom: bitmaps[bitmap].obj.destBottom.value, |
| 179 | destRight: bitmaps[bitmap].obj.destRight.value, |
| 180 | width: bitmaps[bitmap].obj.width.value, |
| 181 | height: bitmaps[bitmap].obj.height.value, |
| 182 | bitsPerPixel: bitmaps[bitmap].obj.bitsPerPixel.value, |
| 183 | isCompress: isCompress, |
| 184 | data: bitmapData |
| 185 | }); |
| 186 | } |
| 187 | }).on('error', function (err) { |
| 188 | log.warn(err.code + '(' + err.message + ')\n' + err.stack); |
| 189 | if (err instanceof error.FatalError) { throw err; } else { self.emit('error', err); } |
| 190 | }); |
| 191 | } |
| 192 | |
| 193 | inherits(RdpClient, events.EventEmitter); |
| 194 | |
| 195 | /** |
| 196 | * Connect RDP client |
| 197 | * @param host {string} destination host |
| 198 | * @param port {integer} destination port |
| 199 | */ |
| 200 | RdpClient.prototype.connect = function (host, port) { |
| 201 | log.debug('connect to ' + host + ':' + port); |
| 202 | var self = this; |
| 203 | this.bufferLayer.socket.connect(port, host, function () { |
| 204 | // in client mode connection start from x224 layer |
| 205 | self.x224.connect(); |
| 206 | }); |
| 207 | return this; |
| 208 | }; |
| 209 | |
| 210 | /** |
| 211 | * Close RDP client |
| 212 | */ |
| 213 | RdpClient.prototype.close = function () { |
| 214 | if (this.connected) { |
| 215 | this.global.close(); |
| 216 | } |
| 217 | this.connected = false; |
| 218 | return this; |
| 219 | }; |
| 220 | |
| 221 | /** |
| 222 | * Send pointer event to server |
| 223 | * @param x {integer} mouse x position |
| 224 | * @param y {integer} mouse y position |
| 225 | * @param button {integer} button number of mouse |
| 226 | * @param isPressed {boolean} state of button |
| 227 | */ |
| 228 | RdpClient.prototype.sendPointerEvent = function (x, y, button, isPressed) { |
| 229 | if (!this.connected) |
| 230 | return; |
| 231 | |
| 232 | var event = pdu.data.pointerEvent(); |
| 233 | if (isPressed) { |
| 234 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN; |
| 235 | } |
| 236 | |
| 237 | switch (button) { |
| 238 | case 1: |
| 239 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1; |
| 240 | break; |
| 241 | case 2: |
| 242 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2; |
| 243 | break; |
| 244 | case 3: |
| 245 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3 |
| 246 | break; |
| 247 | default: |
| 248 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE; |
| 249 | } |
| 250 | |
| 251 | event.obj.xPos.value = x; |
| 252 | event.obj.yPos.value = y; |
| 253 | |
| 254 | this.global.sendInputEvents([event]); |
| 255 | }; |
| 256 | |
| 257 | /** |
| 258 | * send scancode event |
| 259 | * @param code {integer} |
| 260 | * @param isPressed {boolean} |
| 261 | * @param extended {boolenan} extended keys |
| 262 | */ |
| 263 | RdpClient.prototype.sendKeyEventScancode = function (code, isPressed, extended) { |
| 264 | if (!this.connected) |
| 265 | return; |
| 266 | extended = extended || false; |
| 267 | var event = pdu.data.scancodeKeyEvent(); |
| 268 | event.obj.keyCode.value = code; |
| 269 | |
| 270 | if (!isPressed) { |
| 271 | event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE; |
| 272 | } |
| 273 | |
| 274 | if (extended) { |
| 275 | event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED; |
| 276 | } |
| 277 | |
| 278 | this.global.sendInputEvents([event]); |
| 279 | }; |
| 280 | |
| 281 | /** |
| 282 | * Send key event as unicode |
| 283 | * @param code {integer} |
| 284 | * @param isPressed {boolean} |
| 285 | */ |
| 286 | RdpClient.prototype.sendKeyEventUnicode = function (code, isPressed) { |
| 287 | if (!this.connected) |
| 288 | return; |
| 289 | |
| 290 | var event = pdu.data.unicodeKeyEvent(); |
| 291 | event.obj.unicode.value = code; |
| 292 | |
| 293 | if (!isPressed) { |
| 294 | event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE; |
| 295 | } |
| 296 | this.global.sendInputEvents([event]); |
| 297 | } |
| 298 | |
| 299 | /** |
| 300 | * Wheel mouse event |
| 301 | * @param x {integer} mouse x position |
| 302 | * @param y {integer} mouse y position |
| 303 | * @param step {integer} wheel step |
| 304 | * @param isNegative {boolean} |
| 305 | * @param isHorizontal {boolean} |
| 306 | */ |
| 307 | RdpClient.prototype.sendWheelEvent = function (x, y, step, isNegative, isHorizontal) { |
| 308 | if (!this.connected) |
| 309 | return; |
| 310 | |
| 311 | var event = pdu.data.pointerEvent(); |
| 312 | if (isHorizontal) { |
| 313 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL; |
| 314 | } |
| 315 | else { |
| 316 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL; |
| 317 | } |
| 318 | |
| 319 | |
| 320 | if (isNegative) { |
| 321 | event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE; |
| 322 | } |
| 323 | |
| 324 | event.obj.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask) |
| 325 | |
| 326 | event.obj.xPos.value = x; |
| 327 | event.obj.yPos.value = y; |
| 328 | |
| 329 | this.global.sendInputEvents([event]); |
| 330 | } |
| 331 | |
| 332 | /** |
| 333 | * Clipboard event |
| 334 | * @param data {String} content for clipboard |
| 335 | */ |
| 336 | RdpClient.prototype.setClipboardData = function (content) { |
| 337 | this.cliprdr.setClipboardData(content); |
| 338 | } |
| 339 | |
| 340 | function createClient(config) { |
| 341 | return new RdpClient(config); |
| 342 | }; |
| 343 | |
| 344 | /** |
| 345 | * RDP server side protocol |
| 346 | * @param config {object} configuration |
| 347 | * @param socket {net.Socket} |
| 348 | */ |
| 349 | function RdpServer(config, socket) { |
| 350 | if (!(config.key && config.cert)) { |
| 351 | throw new error.FatalError('NODE_RDP_PROTOCOL_RDP_SERVER_CONFIG_MISSING', 'missing cryptographic tools') |
| 352 | } |
| 353 | this.connected = false; |
| 354 | this.bufferLayer = new layer.BufferLayer(socket); |
| 355 | this.tpkt = new TPKT(this.bufferLayer); |
| 356 | this.x224 = new x224.Server(this.tpkt, config.key, config.cert); |
| 357 | this.mcs = new t125.mcs.Server(this.x224); |
| 358 | }; |
| 359 | |
| 360 | inherits(RdpServer, events.EventEmitter); |
| 361 | |
| 362 | function createServer(config, next) { |
| 363 | return net.createServer(function (socket) { |
| 364 | next(new RdpServer(config, socket)); |
| 365 | }); |
| 366 | }; |
| 367 | |
| 368 | /** |
| 369 | * Module exports |
| 370 | */ |
| 371 | module.exports = { |
| 372 | createClient: createClient, |
| 373 | createServer: createServer |
| 374 | }; |