EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
rdp.js
Go to the documentation of this file.
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
20var net = require('net');
21var inherits = require('util').inherits;
22var events = require('events');
23var layer = require('../core').layer;
24var error = require('../core').error;
25var rle = require('../core').rle;
26var log = require('../core').log;
27var TPKT = require('./tpkt');
28var x224 = require('./x224');
29var t125 = require('./t125');
30var pdu = require('./pdu');
31
32/**
33 * decompress bitmap from RLE algorithm
34 * @param bitmap {object} bitmap object of bitmap event of node-rdpjs
35 */
36function 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 */
82function 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
193inherits(RdpClient, events.EventEmitter);
194
195/**
196 * Connect RDP client
197 * @param host {string} destination host
198 * @param port {integer} destination port
199 */
200RdpClient.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 */
213RdpClient.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 */
228RdpClient.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 */
263RdpClient.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 */
286RdpClient.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 */
307RdpClient.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 */
336RdpClient.prototype.setClipboardData = function (content) {
337 this.cliprdr.setClipboardData(content);
338}
339
340function 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 */
349function 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
360inherits(RdpServer, events.EventEmitter);
361
362function createServer(config, next) {
363 return net.createServer(function (socket) {
364 next(new RdpServer(config, socket));
365 });
366};
367
368/**
369 * Module exports
370 */
371module.exports = {
372 createClient: createClient,
373 createServer: createServer
374};