491 lines
14 KiB
JavaScript
491 lines
14 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 inherits = require('util').inherits;
|
|
var events = require('events');
|
|
var type = require('../../core').type;
|
|
var log = require('../../core').log;
|
|
var error = require('../../core').error;
|
|
var gcc = require('./gcc');
|
|
var per = require('./per');
|
|
var asn1 = require('../../asn1');
|
|
|
|
var Message = {
|
|
MCS_TYPE_CONNECT_INITIAL : 0x65,
|
|
MCS_TYPE_CONNECT_RESPONSE : 0x66
|
|
};
|
|
|
|
var DomainMCSPDU = {
|
|
ERECT_DOMAIN_REQUEST : 1,
|
|
DISCONNECT_PROVIDER_ULTIMATUM : 8,
|
|
ATTACH_USER_REQUEST : 10,
|
|
ATTACH_USER_CONFIRM : 11,
|
|
CHANNEL_JOIN_REQUEST : 14,
|
|
CHANNEL_JOIN_CONFIRM : 15,
|
|
SEND_DATA_REQUEST : 25,
|
|
SEND_DATA_INDICATION : 26
|
|
};
|
|
|
|
var Channel = {
|
|
MCS_GLOBAL_CHANNEL : 1003,
|
|
MCS_USERCHANNEL_BASE : 1001
|
|
};
|
|
|
|
/**
|
|
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
|
|
* @returns {asn1.univ.Sequence}
|
|
*/
|
|
function DomainParameters(maxChannelIds, maxUserIds, maxTokenIds,
|
|
numPriorities, minThoughput, maxHeight, maxMCSPDUsize, protocolVersion) {
|
|
return new asn1.univ.Sequence({
|
|
maxChannelIds : new asn1.univ.Integer(maxChannelIds),
|
|
maxUserIds : new asn1.univ.Integer(maxUserIds),
|
|
maxTokenIds : new asn1.univ.Integer(maxTokenIds),
|
|
numPriorities : new asn1.univ.Integer(numPriorities),
|
|
minThoughput : new asn1.univ.Integer(minThoughput),
|
|
maxHeight : new asn1.univ.Integer(maxHeight),
|
|
maxMCSPDUsize : new asn1.univ.Integer(maxMCSPDUsize),
|
|
protocolVersion : new asn1.univ.Integer(protocolVersion)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
|
|
* @param userData {Buffer}
|
|
* @returns {asn1.univ.Sequence}
|
|
*/
|
|
function ConnectInitial (userData) {
|
|
return new asn1.univ.Sequence({
|
|
callingDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
|
|
calledDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
|
|
upwardFlag : new asn1.univ.Boolean(true),
|
|
targetParameters : DomainParameters(34, 2, 0, 1, 0, 1, 0xffff, 2),
|
|
minimumParameters : DomainParameters(1, 1, 1, 1, 0, 1, 0x420, 2),
|
|
maximumParameters : DomainParameters(0xffff, 0xfc17, 0xffff, 1, 0, 1, 0xffff, 2),
|
|
userData : new asn1.univ.OctetString(userData)
|
|
}).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 101));
|
|
}
|
|
|
|
/**
|
|
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
|
|
* @returns {asn1.univ.Sequence}
|
|
*/
|
|
function ConnectResponse (userData) {
|
|
return new asn1.univ.Sequence({
|
|
result : new asn1.univ.Enumerate(0),
|
|
calledConnectId : new asn1.univ.Integer(0),
|
|
domainParameters : DomainParameters(22, 3, 0, 1, 0, 1,0xfff8, 2),
|
|
userData : new asn1.univ.OctetString(userData)
|
|
}).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 102));
|
|
}
|
|
|
|
/**
|
|
* Format MCS PDU header packet
|
|
* @param mcsPdu {integer}
|
|
* @param options {integer}
|
|
* @returns {type.UInt8} headers
|
|
*/
|
|
function writeMCSPDUHeader(mcsPdu, options) {
|
|
options = options || 0;
|
|
return new type.UInt8((mcsPdu << 2) | options);
|
|
}
|
|
|
|
/**
|
|
* Read MCS PDU header
|
|
* @param opcode
|
|
* @param mcsPdu
|
|
* @returns {Boolean}
|
|
*/
|
|
function readMCSPDUHeader(opcode, mcsPdu) {
|
|
return (opcode >> 2) === mcsPdu;
|
|
}
|
|
|
|
/**
|
|
* Multi-Channel Services
|
|
* @param transport {events.EventEmitter} transport layer listen (connect, data) events
|
|
* @param recvOpCode {DomainMCSPDU} opcode use in receive automata
|
|
* @param sendOpCode {DomainMCSPDU} opcode use to send message
|
|
*/
|
|
function MCS(transport, recvOpCode, sendOpCode) {
|
|
this.transport = transport;
|
|
this.recvOpCode = recvOpCode;
|
|
this.sendOpCode = sendOpCode;
|
|
this.channels = [{id : Channel.MCS_GLOBAL_CHANNEL, name : 'global'}];
|
|
this.channels.find = function(callback) {
|
|
for(var i in this) {
|
|
if(callback(this[i])) return this[i];
|
|
};
|
|
};
|
|
|
|
// bind events
|
|
var self = this;
|
|
this.transport.on('close', function () {
|
|
self.emit('close');
|
|
}).on('error', function (err) {
|
|
self.emit('error', err);
|
|
});
|
|
}
|
|
|
|
//inherit from Layer
|
|
inherits(MCS, events.EventEmitter);
|
|
|
|
/**
|
|
* Send message to a specific channel
|
|
* @param channelName {string} name of channel
|
|
* @param data {type.*} message to send
|
|
*/
|
|
MCS.prototype.send = function(channelName, data) {
|
|
var channelId = this.channels.find(function(element) {
|
|
if (element.name === channelName) return true;
|
|
}).id;
|
|
|
|
this.transport.send(new type.Component([
|
|
writeMCSPDUHeader(this.sendOpCode),
|
|
per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
|
|
per.writeInteger16(channelId),
|
|
new type.UInt8(0x70),
|
|
per.writeLength(data.size()),
|
|
data
|
|
]));
|
|
};
|
|
|
|
/**
|
|
* Main receive function
|
|
* @param s {type.Stream}
|
|
*/
|
|
MCS.prototype.recv = function(s) {
|
|
opcode = new type.UInt8().read(s).value;
|
|
|
|
if (readMCSPDUHeader(opcode, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM)) {
|
|
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM");
|
|
this.transport.close();
|
|
return
|
|
}
|
|
else if(!readMCSPDUHeader(opcode, this.recvOpCode)) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_RECEIVE_OPCODE');
|
|
}
|
|
|
|
per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
|
|
|
|
var channelId = per.readInteger16(s);
|
|
|
|
per.readEnumerates(s);
|
|
per.readLength(s);
|
|
|
|
var channelName = this.channels.find(function(e) {
|
|
if (e.id === channelId) return true;
|
|
}).name;
|
|
|
|
this.emit(channelName, s);
|
|
};
|
|
|
|
/**
|
|
* Only main channels handle actually
|
|
* @param transport {event.EventEmitter} bind connect and data events
|
|
* @returns
|
|
*/
|
|
function Client(transport) {
|
|
MCS.call(this, transport, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST);
|
|
|
|
// channel context automata
|
|
this.channelsConnected = 0;
|
|
|
|
// init gcc information
|
|
this.clientCoreData = gcc.clientCoreData();
|
|
this.clientNetworkData = gcc.clientNetworkData(new type.Component([]));
|
|
this.clientSecurityData = gcc.clientSecurityData();
|
|
|
|
// must be readed from protocol
|
|
this.serverCoreData = null;
|
|
this.serverSecurityData = null;
|
|
this.serverNetworkData = null;
|
|
|
|
var self = this;
|
|
this.transport.on('connect', function(s) {
|
|
self.connect(s);
|
|
});
|
|
}
|
|
|
|
inherits(Client, MCS);
|
|
|
|
/**
|
|
* Connect event layer
|
|
*/
|
|
Client.prototype.connect = function(selectedProtocol) {
|
|
this.clientCoreData.obj.serverSelectedProtocol.value = selectedProtocol;
|
|
this.sendConnectInitial();
|
|
};
|
|
|
|
/**
|
|
* close stack
|
|
*/
|
|
Client.prototype.close = function() {
|
|
this.transport.close();
|
|
};
|
|
|
|
/**
|
|
* MCS connect response (server->client)
|
|
* @param s {type.Stream}
|
|
*/
|
|
Client.prototype.recvConnectResponse = function(s) {
|
|
var userData = new type.Stream(ConnectResponse().decode(s, asn1.ber).value.userData.value);
|
|
var serverSettings = gcc.readConferenceCreateResponse(userData);
|
|
// record server gcc block
|
|
for(var i in serverSettings) {
|
|
if(!serverSettings[i].obj) {
|
|
continue;
|
|
}
|
|
switch(serverSettings[i].obj.__TYPE__) {
|
|
case gcc.MessageType.SC_CORE:
|
|
this.serverCoreData = serverSettings[i];
|
|
break;
|
|
case gcc.MessageType.SC_SECURITY:
|
|
this.serverSecurityData = serverSettings[i];
|
|
break;
|
|
case gcc.MessageType.SC_NET:
|
|
this.serverNetworkData = serverSettings[i];
|
|
break;
|
|
default:
|
|
log.warn('unhandle server gcc block : ' + serverSettings[i].obj.__TYPE__);
|
|
}
|
|
}
|
|
|
|
// send domain request
|
|
this.sendErectDomainRequest();
|
|
// send attach user request
|
|
this.sendAttachUserRequest();
|
|
// now wait user confirm from server
|
|
var self = this;
|
|
this.transport.once('data', function(s) {
|
|
self.recvAttachUserConfirm(s);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* MCS connection automata step
|
|
* @param s {type.Stream}
|
|
*/
|
|
Client.prototype.recvAttachUserConfirm = function(s) {
|
|
if (!readMCSPDUHeader(new type.UInt8().read(s).value, DomainMCSPDU.ATTACH_USER_CONFIRM)) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER');
|
|
}
|
|
|
|
if (per.readEnumerates(s) !== 0) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_REJECT_USER');
|
|
}
|
|
|
|
this.userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
|
|
//ask channel for specific user
|
|
this.channels.push({ id : this.userId, name : 'user' });
|
|
// channel connect automata
|
|
this.connectChannels();
|
|
};
|
|
|
|
/**
|
|
* Last state in channel connection automata
|
|
* @param s {type.Stream}
|
|
*/
|
|
Client.prototype.recvChannelJoinConfirm = function(s) {
|
|
var opcode = new type.UInt8().read(s).value;
|
|
|
|
if (!readMCSPDUHeader(opcode, DomainMCSPDU.CHANNEL_JOIN_CONFIRM)) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM');
|
|
}
|
|
|
|
var confirm = per.readEnumerates(s);
|
|
|
|
var userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
|
|
if (this.userId !== userId) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID');
|
|
}
|
|
|
|
var channelId = per.readInteger16(s);
|
|
|
|
if ((confirm !== 0) && (channelId === Channel.MCS_GLOBAL_CHANNEL || channelId === this.userId)) {
|
|
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL');
|
|
}
|
|
|
|
this.connectChannels();
|
|
};
|
|
|
|
/**
|
|
* First MCS message
|
|
*/
|
|
Client.prototype.sendConnectInitial = function() {
|
|
|
|
var ccReq = gcc.writeConferenceCreateRequest(new type.Component([
|
|
gcc.block(this.clientCoreData),
|
|
gcc.block(this.clientNetworkData),
|
|
gcc.block(this.clientSecurityData)
|
|
])).toStream().getValue();
|
|
|
|
this.transport.send(ConnectInitial(ccReq).encode(asn1.ber));
|
|
|
|
// next event is connect response
|
|
var self = this;
|
|
this.transport.once('data', function(s) {
|
|
self.recvConnectResponse(s);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* MCS connection automata step
|
|
*/
|
|
Client.prototype.sendErectDomainRequest = function() {
|
|
this.transport.send(new type.Component([
|
|
writeMCSPDUHeader(DomainMCSPDU.ERECT_DOMAIN_REQUEST),
|
|
per.writeInteger(0),
|
|
per.writeInteger(0)
|
|
]));
|
|
};
|
|
|
|
/**
|
|
* MCS connection automata step
|
|
*/
|
|
Client.prototype.sendAttachUserRequest = function() {
|
|
this.transport.send(writeMCSPDUHeader(DomainMCSPDU.ATTACH_USER_REQUEST));
|
|
};
|
|
|
|
/**
|
|
* Send a channel join request
|
|
* @param channelId {integer} channel id
|
|
*/
|
|
Client.prototype.sendChannelJoinRequest = function(channelId) {
|
|
this.transport.send(new type.Component([
|
|
writeMCSPDUHeader(DomainMCSPDU.CHANNEL_JOIN_REQUEST),
|
|
per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
|
|
per.writeInteger16(channelId)
|
|
]));
|
|
};
|
|
|
|
/**
|
|
* Connect channels automata
|
|
* @param s {type.Stream}
|
|
*/
|
|
Client.prototype.connectChannels = function(s) {
|
|
if(this.channelsConnected == this.channels.length) {
|
|
var self = this;
|
|
this.transport.on('data', function(s) {
|
|
self.recv(s);
|
|
});
|
|
|
|
// send client and sever gcc informations
|
|
this.emit('connect',
|
|
{
|
|
core : this.clientCoreData.obj,
|
|
security : this.clientSecurityData.obj,
|
|
net : this.clientNetworkData.obj
|
|
},
|
|
{
|
|
core : this.serverCoreData.obj,
|
|
security : this.serverSecurityData.obj
|
|
}, this.userId, this.channels);
|
|
return;
|
|
}
|
|
|
|
this.sendChannelJoinRequest(this.channels[this.channelsConnected++].id);
|
|
|
|
var self = this;
|
|
this.transport.once('data', function(s) {
|
|
self.recvChannelJoinConfirm(s);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Server side of MCS layer
|
|
* @param transport
|
|
*/
|
|
function Server (transport) {
|
|
MCS.call(this, transport, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION);
|
|
|
|
// must be readed from protocol
|
|
this.clientCoreData = null;
|
|
this.clientNetworkData = null;
|
|
this.clientSecurityData = null;
|
|
|
|
// init gcc information
|
|
this.serverCoreData = gcc.serverCoreData();
|
|
this.serverSecurityData = gcc.serverSecurityData();
|
|
this.serverNetworkData = gcc.serverNetworkData(new type.Component([]));
|
|
|
|
var self = this;
|
|
this.transport.on('connect', function (selectedProtocol) {
|
|
self.serverCoreData.obj.clientRequestedProtocol.value = selectedProtocol;
|
|
}).once('data', function (s) {
|
|
self.recvConnectInitial(s);
|
|
});
|
|
}
|
|
|
|
inherits(Server, MCS);
|
|
|
|
/**
|
|
* First state of server automata
|
|
* @param s {type.Stream}
|
|
*/
|
|
Server.prototype.recvConnectInitial = function (s) {
|
|
var userData = new type.Stream(ConnectInitial().decode(s, asn1.ber).value.userData.value);
|
|
var clientSettings = gcc.readConferenceCreateRequest(userData);
|
|
// record server gcc block
|
|
for(var i in clientSettings) {
|
|
if(!clientSettings[i].obj) {
|
|
continue;
|
|
}
|
|
switch(clientSettings[i].obj.__TYPE__) {
|
|
case gcc.MessageType.CS_CORE:
|
|
this.clientCoreData = clientSettings[i];
|
|
break;
|
|
case gcc.MessageType.CS_SECURITY:
|
|
this.clientSecurityData = clientSettings[i];
|
|
break;
|
|
case gcc.MessageType.CS_NET:
|
|
this.clientNetworkData = clientSettings[i];
|
|
for (var i = 0; i < this.clientNetworkData.obj.channelCount.value; i++) {
|
|
this.serverNetworkData.obj.channelIdArray.obj.push(new type.UInt16Le( i + 1 + Channel.MCS_GLOBAL_CHANNEL));
|
|
}
|
|
break;
|
|
default:
|
|
log.debug('unhandle client gcc block : ' + clientSettings[i].obj.__TYPE__);
|
|
}
|
|
}
|
|
|
|
this.sendConnectResponse();
|
|
};
|
|
|
|
/**
|
|
* State 2 in mcs server connetion automata
|
|
*/
|
|
Server.prototype.sendConnectResponse = function () {
|
|
var ccReq = gcc.writeConferenceCreateResponse(new type.Component([
|
|
gcc.block(this.serverCoreData),
|
|
gcc.block(this.serverSecurityData),
|
|
gcc.block(this.serverNetworkData),
|
|
])).toStream().getValue();
|
|
|
|
this.transport.send(ConnectResponse(ccReq).encode(asn1.ber));
|
|
|
|
};
|
|
|
|
/**
|
|
* Module exports
|
|
*/
|
|
module.exports = {
|
|
Client : Client,
|
|
Server : Server
|
|
};
|