EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
mcs.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 inherits = require('util').inherits;
21var events = require('events');
22var type = require('../../core').type;
23var log = require('../../core').log;
24var error = require('../../core').error;
25var gcc = require('./gcc');
26var per = require('./per');
27var asn1 = require('../../asn1');
28var cliprdr = require('../pdu/cliprdr');
29
30var Message = {
31 MCS_TYPE_CONNECT_INITIAL : 0x65,
32 MCS_TYPE_CONNECT_RESPONSE : 0x66
33};
34
35var DomainMCSPDU = {
36 ERECT_DOMAIN_REQUEST : 1,
37 DISCONNECT_PROVIDER_ULTIMATUM : 8,
38 ATTACH_USER_REQUEST : 10,
39 ATTACH_USER_CONFIRM : 11,
40 CHANNEL_JOIN_REQUEST : 14,
41 CHANNEL_JOIN_CONFIRM : 15,
42 SEND_DATA_REQUEST : 25,
43 SEND_DATA_INDICATION : 26
44};
45
46var Channel = {
47 MCS_GLOBAL_CHANNEL: 1003,
48 MCS_USERCHANNEL_BASE: 1001,
49 MCS_CLIPRDR_CHANNEL: 1005
50};
51
52/**
53 * Channel Defined
54 */
55const RdpdrChannelDef = new type.Component({
56 name: new type.BinaryString(Buffer.from('rdpdr' + '\x00\x00\x00', 'binary'), { readLength: new type.CallableValue(8) }),
57 options: new type.UInt32Le(0x80800000)
58});
59
60const RdpsndChannelDef = new type.Component({
61 name: new type.BinaryString(Buffer.from('rdpsnd' + '\x00\x00', 'binary'), { readLength: new type.CallableValue(8) }),
62 options: new type.UInt32Le(0xc0000000)
63});
64
65const CliprdrChannelDef = new type.Component({
66 name: new type.BinaryString(Buffer.from('cliprdr' + '\x00', 'binary'), { readLength: new type.CallableValue(8) }),
67 // CHANNEL_OPTION_INITIALIZED |
68 // CHANNEL_OPTION_ENCRYPT_RDP |
69 // CHANNEL_OPTION_COMPRESS_RDP |
70 // CHANNEL_OPTION_SHOW_PROTOCOL
71 options: new type.UInt32Le(0xc0a00000)
72});
73
74/**
75 * @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
76 * @returns {asn1.univ.Sequence}
77 */
78function DomainParameters(maxChannelIds, maxUserIds, maxTokenIds,
79 numPriorities, minThoughput, maxHeight, maxMCSPDUsize, protocolVersion) {
80 return new asn1.univ.Sequence({
81 maxChannelIds : new asn1.univ.Integer(maxChannelIds),
82 maxUserIds : new asn1.univ.Integer(maxUserIds),
83 maxTokenIds : new asn1.univ.Integer(maxTokenIds),
84 numPriorities : new asn1.univ.Integer(numPriorities),
85 minThoughput : new asn1.univ.Integer(minThoughput),
86 maxHeight : new asn1.univ.Integer(maxHeight),
87 maxMCSPDUsize : new asn1.univ.Integer(maxMCSPDUsize),
88 protocolVersion : new asn1.univ.Integer(protocolVersion)
89 });
90}
91
92/**
93 * @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
94 * @param userData {Buffer}
95 * @returns {asn1.univ.Sequence}
96 */
97function ConnectInitial (userData) {
98 return new asn1.univ.Sequence({
99 callingDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
100 calledDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
101 upwardFlag : new asn1.univ.Boolean(true),
102 targetParameters : DomainParameters(34, 2, 0, 1, 0, 1, 0xffff, 2),
103 minimumParameters : DomainParameters(1, 1, 1, 1, 0, 1, 0x420, 2),
104 maximumParameters : DomainParameters(0xffff, 0xfc17, 0xffff, 1, 0, 1, 0xffff, 2),
105 userData : new asn1.univ.OctetString(userData)
106 }).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 101));
107}
108
109/**
110 * @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
111 * @returns {asn1.univ.Sequence}
112 */
113function ConnectResponse (userData) {
114 return new asn1.univ.Sequence({
115 result : new asn1.univ.Enumerate(0),
116 calledConnectId : new asn1.univ.Integer(0),
117 domainParameters : DomainParameters(22, 3, 0, 1, 0, 1,0xfff8, 2),
118 userData : new asn1.univ.OctetString(userData)
119 }).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 102));
120}
121
122/**
123 * Format MCS PDU header packet
124 * @param mcsPdu {integer}
125 * @param options {integer}
126 * @returns {type.UInt8} headers
127 */
128function writeMCSPDUHeader(mcsPdu, options) {
129 options = options || 0;
130 return new type.UInt8((mcsPdu << 2) | options);
131}
132
133/**
134 * Read MCS PDU header
135 * @param opcode
136 * @param mcsPdu
137 * @returns {Boolean}
138 */
139function readMCSPDUHeader(opcode, mcsPdu) {
140 return (opcode >> 2) === mcsPdu;
141}
142
143/**
144 * Multi-Channel Services
145 * @param transport {events.EventEmitter} transport layer listen (connect, data) events
146 * @param recvOpCode {DomainMCSPDU} opcode use in receive automata
147 * @param sendOpCode {DomainMCSPDU} opcode use to send message
148 */
149function MCS(transport, recvOpCode, sendOpCode) {
150 this.transport = transport;
151 this.recvOpCode = recvOpCode;
152 this.sendOpCode = sendOpCode;
153 this.channels = [
154 { id: Channel.MCS_GLOBAL_CHANNEL, name: 'global' },
155 { id: Channel.MCS_CLIPRDR_CHANNEL, name: 'cliprdr' }
156 ];
157 this.channels.find = function(callback) {
158 for(var i in this) {
159 if(callback(this[i])) return this[i];
160 };
161 };
162
163 // bind events
164 var self = this;
165 this.transport.on('close', function () {
166 self.emit('close');
167 }).on('error', function (err) {
168 self.emit('error', err);
169 });
170}
171
172//inherit from Layer
173inherits(MCS, events.EventEmitter);
174
175/**
176 * Send message to a specific channel
177 * @param channelName {string} name of channel
178 * @param data {type.*} message to send
179 */
180MCS.prototype.send = function(channelName, data) {
181 var channelId = this.channels.find(function(element) {
182 if (element.name === channelName) return true;
183 }).id;
184
185 this.transport.send(new type.Component([
186 writeMCSPDUHeader(this.sendOpCode),
187 per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
188 per.writeInteger16(channelId),
189 new type.UInt8(0x70),
190 per.writeLength(data.size()),
191 data
192 ]));
193};
194
195/**
196 * Main receive function
197 * @param s {type.Stream}
198 */
199MCS.prototype.recv = function(s) {
200 opcode = new type.UInt8().read(s).value;
201
202 if (readMCSPDUHeader(opcode, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM)) {
203 log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM");
204 this.transport.close();
205 return
206 }
207 else if(!readMCSPDUHeader(opcode, this.recvOpCode)) {
208 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_RECEIVE_OPCODE');
209 }
210
211 per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
212
213 var channelId = per.readInteger16(s);
214
215 per.readEnumerates(s);
216 per.readLength(s);
217
218 var channelName = this.channels.find(function(e) {
219 if (e.id === channelId) return true;
220 }).name;
221
222 this.emit(channelName, s);
223};
224
225/**
226 * Only main channels handle actually
227 * @param transport {event.EventEmitter} bind connect and data events
228 * @returns
229 */
230function Client(transport) {
231 MCS.call(this, transport, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST);
232
233 // channel context automata
234 this.channelsConnected = 0;
235
236 // init gcc information
237 this.clientCoreData = gcc.clientCoreData();
238 // cliprdr channel
239 this.clientNetworkData = gcc.clientNetworkData(new type.Component([RdpdrChannelDef, CliprdrChannelDef, RdpsndChannelDef]));
240 this.clientSecurityData = gcc.clientSecurityData();
241
242 // must be read from protocol
243 this.serverCoreData = null;
244 this.serverSecurityData = null;
245 this.serverNetworkData = null;
246
247 var self = this;
248 this.transport.on('connect', function(s) {
249 self.connect(s);
250 });
251}
252
253inherits(Client, MCS);
254
255/**
256 * Connect event layer
257 */
258Client.prototype.connect = function(selectedProtocol) {
259 this.clientCoreData.obj.serverSelectedProtocol.value = selectedProtocol;
260 this.sendConnectInitial();
261};
262
263/**
264 * close stack
265 */
266Client.prototype.close = function() {
267 this.transport.close();
268};
269
270/**
271 * MCS connect response (server->client)
272 * @param s {type.Stream}
273 */
274Client.prototype.recvConnectResponse = function(s) {
275 var userData = new type.Stream(ConnectResponse().decode(s, asn1.ber).value.userData.value);
276 var serverSettings = gcc.readConferenceCreateResponse(userData);
277 // record server gcc block
278 for(var i in serverSettings) {
279 if(!serverSettings[i].obj) {
280 continue;
281 }
282 switch(serverSettings[i].obj.__TYPE__) {
283 case gcc.MessageType.SC_CORE:
284 this.serverCoreData = serverSettings[i];
285 break;
286 case gcc.MessageType.SC_SECURITY:
287 this.serverSecurityData = serverSettings[i];
288 break;
289 case gcc.MessageType.SC_NET:
290 this.serverNetworkData = serverSettings[i];
291 break;
292 default:
293 log.warn('unhandle server gcc block : ' + serverSettings[i].obj.__TYPE__);
294 }
295 }
296
297 // send domain request
298 this.sendErectDomainRequest();
299 // send attach user request
300 this.sendAttachUserRequest();
301 // now wait user confirm from server
302 var self = this;
303 this.transport.once('data', function(s) {
304 self.recvAttachUserConfirm(s);
305 });
306};
307
308/**
309 * MCS connection automata step
310 * @param s {type.Stream}
311 */
312Client.prototype.recvAttachUserConfirm = function(s) {
313 if (!readMCSPDUHeader(new type.UInt8().read(s).value, DomainMCSPDU.ATTACH_USER_CONFIRM)) {
314 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER');
315 }
316
317 if (per.readEnumerates(s) !== 0) {
318 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_REJECT_USER');
319 }
320
321 this.userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
322 //ask channel for specific user
323 this.channels.push({ id : this.userId, name : 'user' });
324 // channel connect automata
325 this.connectChannels();
326};
327
328/**
329 * Last state in channel connection automata
330 * @param s {type.Stream}
331 */
332Client.prototype.recvChannelJoinConfirm = function(s) {
333 var opcode = new type.UInt8().read(s).value;
334
335 if (!readMCSPDUHeader(opcode, DomainMCSPDU.CHANNEL_JOIN_CONFIRM)) {
336 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM');
337 }
338
339 var confirm = per.readEnumerates(s);
340
341 var userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
342 if (this.userId !== userId) {
343 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID');
344 }
345
346 var channelId = per.readInteger16(s);
347
348 //if ((confirm !== 0) && (channelId === Channel.MCS_GLOBAL_CHANNEL || channelId === this.userId)) {
349 if ((confirm !== 0) && (channelId === Channel.MCS_CLIPRDR_CHANNEL || channelId === Channel.MCS_GLOBAL_CHANNEL || channelId === this.userId)) {
350 throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL');
351 }
352
353 this.connectChannels();
354};
355
356/**
357 * First MCS message
358 */
359Client.prototype.sendConnectInitial = function() {
360
361 var ccReq = gcc.writeConferenceCreateRequest(new type.Component([
362 gcc.block(this.clientCoreData),
363 gcc.block(this.clientNetworkData),
364 gcc.block(this.clientSecurityData)
365 ])).toStream().getValue();
366
367 this.transport.send(ConnectInitial(ccReq).encode(asn1.ber));
368
369 // next event is connect response
370 var self = this;
371 this.transport.once('data', function(s) {
372 self.recvConnectResponse(s);
373 });
374};
375
376/**
377 * MCS connection automata step
378 */
379Client.prototype.sendErectDomainRequest = function() {
380 this.transport.send(new type.Component([
381 writeMCSPDUHeader(DomainMCSPDU.ERECT_DOMAIN_REQUEST),
382 per.writeInteger(0),
383 per.writeInteger(0)
384 ]));
385};
386
387/**
388 * MCS connection automata step
389 */
390Client.prototype.sendAttachUserRequest = function() {
391 this.transport.send(writeMCSPDUHeader(DomainMCSPDU.ATTACH_USER_REQUEST));
392};
393
394/**
395 * Send a channel join request
396 * @param channelId {integer} channel id
397 */
398Client.prototype.sendChannelJoinRequest = function(channelId) {
399 this.transport.send(new type.Component([
400 writeMCSPDUHeader(DomainMCSPDU.CHANNEL_JOIN_REQUEST),
401 per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
402 per.writeInteger16(channelId)
403 ]));
404};
405
406/**
407 * Connect channels automata
408 * @param s {type.Stream}
409 */
410Client.prototype.connectChannels = function(s) {
411 if(this.channelsConnected == this.channels.length) {
412 var self = this;
413 this.transport.on('data', function(s) {
414 self.recv(s);
415 });
416
417 // send client and sever gcc information
418 this.emit('connect',
419 {
420 core : this.clientCoreData.obj,
421 security : this.clientSecurityData.obj,
422 net : this.clientNetworkData.obj
423 },
424 {
425 core : this.serverCoreData.obj,
426 security : this.serverSecurityData.obj
427 }, this.userId, this.channels);
428 return;
429 }
430
431 this.sendChannelJoinRequest(this.channels[this.channelsConnected++].id);
432
433 var self = this;
434 this.transport.once('data', function(s) {
435 self.recvChannelJoinConfirm(s);
436 });
437};
438
439/**
440 * Server side of MCS layer
441 * @param transport
442 */
443function Server (transport) {
444 MCS.call(this, transport, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION);
445
446 // must be readed from protocol
447 this.clientCoreData = null;
448 this.clientNetworkData = null;
449 this.clientSecurityData = null;
450
451 // init gcc information
452 this.serverCoreData = gcc.serverCoreData();
453 this.serverSecurityData = gcc.serverSecurityData();
454 this.serverNetworkData = gcc.serverNetworkData(new type.Component([]));
455
456 var self = this;
457 this.transport.on('connect', function (selectedProtocol) {
458 self.serverCoreData.obj.clientRequestedProtocol.value = selectedProtocol;
459 }).once('data', function (s) {
460 self.recvConnectInitial(s);
461 });
462}
463
464inherits(Server, MCS);
465
466/**
467 * First state of server automata
468 * @param s {type.Stream}
469 */
470Server.prototype.recvConnectInitial = function (s) {
471 var userData = new type.Stream(ConnectInitial().decode(s, asn1.ber).value.userData.value);
472 var clientSettings = gcc.readConferenceCreateRequest(userData);
473 // record server gcc block
474 for(var i in clientSettings) {
475 if(!clientSettings[i].obj) {
476 continue;
477 }
478 switch(clientSettings[i].obj.__TYPE__) {
479 case gcc.MessageType.CS_CORE:
480 this.clientCoreData = clientSettings[i];
481 break;
482 case gcc.MessageType.CS_SECURITY:
483 this.clientSecurityData = clientSettings[i];
484 break;
485 case gcc.MessageType.CS_NET:
486 this.clientNetworkData = clientSettings[i];
487 for (var i = 0; i < this.clientNetworkData.obj.channelCount.value; i++) {
488 this.serverNetworkData.obj.channelIdArray.obj.push(new type.UInt16Le( i + 1 + Channel.MCS_GLOBAL_CHANNEL));
489 }
490 break;
491 default:
492 log.debug('unhandle client gcc block : ' + clientSettings[i].obj.__TYPE__);
493 }
494 }
495
496 this.sendConnectResponse();
497};
498
499/**
500 * State 2 in mcs server connection automata
501 */
502Server.prototype.sendConnectResponse = function () {
503 var ccReq = gcc.writeConferenceCreateResponse(new type.Component([
504 gcc.block(this.serverCoreData),
505 gcc.block(this.serverSecurityData),
506 gcc.block(this.serverNetworkData),
507 ])).toStream().getValue();
508
509 this.transport.send(ConnectResponse(ccReq).encode(asn1.ber));
510
511};
512
513/**
514 * Module exports
515 */
516module.exports = {
517 Client : Client,
518 Server : Server
519};