| 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 inherits = require('util').inherits; |
| 21 | var events = require('events'); |
| 22 | var type = require('../core').type; |
| 23 | var log = require('../core').log; |
| 24 | var error = require('../core').error; |
| 25 | |
| 26 | /** |
| 27 | * Message type present in X224 packet header |
| 28 | */ |
| 29 | var MessageType = { |
| 30 | X224_TPDU_CONNECTION_REQUEST : 0xE0, |
| 31 | X224_TPDU_CONNECTION_CONFIRM : 0xD0, |
| 32 | X224_TPDU_DISCONNECT_REQUEST : 0x80, |
| 33 | X224_TPDU_DATA : 0xF0, |
| 34 | X224_TPDU_ERROR : 0x70 |
| 35 | }; |
| 36 | |
| 37 | /** |
| 38 | * Type of negotiation present in negotiation packet |
| 39 | */ |
| 40 | var NegotiationType = { |
| 41 | TYPE_RDP_NEG_REQ : 0x01, |
| 42 | TYPE_RDP_NEG_RSP : 0x02, |
| 43 | TYPE_RDP_NEG_FAILURE : 0x03 |
| 44 | }; |
| 45 | |
| 46 | /** |
| 47 | * Protocols available for x224 layer |
| 48 | */ |
| 49 | var Protocols = { |
| 50 | PROTOCOL_RDP : 0x00000000, |
| 51 | PROTOCOL_SSL : 0x00000001, |
| 52 | PROTOCOL_HYBRID : 0x00000002, |
| 53 | PROTOCOL_HYBRID_EX : 0x00000008 |
| 54 | }; |
| 55 | |
| 56 | /** |
| 57 | * Use to negotiate security layer of RDP stack |
| 58 | * In node-rdpjs only ssl is available |
| 59 | * @param opt {object} component type options |
| 60 | * @see request -> http://msdn.microsoft.com/en-us/library/cc240500.aspx |
| 61 | * @see response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx |
| 62 | * @see failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx |
| 63 | */ |
| 64 | function negotiation(opt) { |
| 65 | var self = { |
| 66 | type : new type.UInt8(), |
| 67 | flag : new type.UInt8(), |
| 68 | length : new type.UInt16Le(0x0008, { constant : true }), |
| 69 | result : new type.UInt32Le() |
| 70 | }; |
| 71 | return new type.Component(self, opt); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * X224 client connection request |
| 76 | * @param opt {object} component type options |
| 77 | * @see http://msdn.microsoft.com/en-us/library/cc240470.aspx |
| 78 | */ |
| 79 | function clientConnectionRequestPDU(opt, cookie) { |
| 80 | var self = { |
| 81 | len : new type.UInt8(function() { |
| 82 | return new type.Component(self).size() - 1; |
| 83 | }), |
| 84 | code : new type.UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, { constant : true }), |
| 85 | padding : new type.Component([new type.UInt16Le(), new type.UInt16Le(), new type.UInt8()]), |
| 86 | cookie : cookie || new type.Factory( function (s) { |
| 87 | var offset = 0; |
| 88 | while (true) { |
| 89 | var token = s.buffer.readUInt16LE(s.offset + offset); |
| 90 | if (token === 0x0a0d) { |
| 91 | self.cookie = new type.BinaryString(null, { readLength : new type.CallableValue(offset + 2) }).read(s); |
| 92 | return; |
| 93 | } |
| 94 | else { |
| 95 | offset += 1; |
| 96 | } |
| 97 | } |
| 98 | }, { conditional : function () { |
| 99 | return self.len.value > 14; |
| 100 | }}), |
| 101 | protocolNeg : negotiation({ optional : true }) |
| 102 | }; |
| 103 | |
| 104 | return new type.Component(self, opt); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * X224 Server connection confirm |
| 109 | * @param opt {object} component type options |
| 110 | * @see http://msdn.microsoft.com/en-us/library/cc240506.aspx |
| 111 | */ |
| 112 | function serverConnectionConfirm(opt) { |
| 113 | var self = { |
| 114 | len : new type.UInt8(function() { |
| 115 | return new type.Component(self).size() - 1; |
| 116 | }), |
| 117 | code : new type.UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, { constant : true }), |
| 118 | padding : new type.Component([new type.UInt16Le(), new type.UInt16Le(), new type.UInt8()]), |
| 119 | protocolNeg : negotiation({ optional : true }) |
| 120 | }; |
| 121 | |
| 122 | return new type.Component(self, opt); |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Header of each data message from x224 layer |
| 127 | * @returns {type.Component} |
| 128 | */ |
| 129 | function x224DataHeader() { |
| 130 | var self = { |
| 131 | header : new type.UInt8(2), |
| 132 | messageType : new type.UInt8(MessageType.X224_TPDU_DATA, { constant : true }), |
| 133 | separator : new type.UInt8(0x80, { constant : true }) |
| 134 | }; |
| 135 | return new type.Component(self); |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Common X224 Automata |
| 140 | * @param presentation {Layer} presentation layer |
| 141 | */ |
| 142 | function X224(transport) { |
| 143 | this.transport = transport; |
| 144 | this.requestedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID; |
| 145 | this.selectedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID; |
| 146 | |
| 147 | var self = this; |
| 148 | this.transport.on('close', function() { |
| 149 | self.emit('close'); |
| 150 | }).on('error', function (err) { |
| 151 | self.emit('error', err); |
| 152 | }); |
| 153 | } |
| 154 | |
| 155 | //inherit from Layer |
| 156 | inherits(X224, events.EventEmitter); |
| 157 | |
| 158 | /** |
| 159 | * Main data received function |
| 160 | * after connection sequence |
| 161 | * @param s {type.Stream} stream formatted from transport layer |
| 162 | */ |
| 163 | X224.prototype.recvData = function(s) { |
| 164 | // check header |
| 165 | x224DataHeader().read(s); |
| 166 | this.emit('data', s); |
| 167 | }; |
| 168 | |
| 169 | /** |
| 170 | * Format message from x224 layer to transport layer |
| 171 | * @param message {type} |
| 172 | * @returns {type.Component} x224 formatted message |
| 173 | */ |
| 174 | X224.prototype.send = function(message) { |
| 175 | this.transport.send(new type.Component([x224DataHeader(), message])); |
| 176 | }; |
| 177 | |
| 178 | /** |
| 179 | * Client x224 automata |
| 180 | * @param transport {events.EventEmitter} (bind data events) |
| 181 | */ |
| 182 | function Client(transport, config) { |
| 183 | this.config = config; |
| 184 | X224.call(this, transport); |
| 185 | } |
| 186 | |
| 187 | //inherit from X224 automata |
| 188 | inherits(Client, X224); |
| 189 | |
| 190 | /** |
| 191 | * Client automata connect event |
| 192 | */ |
| 193 | Client.prototype.connect = function() { |
| 194 | var message = clientConnectionRequestPDU(null, new type.BinaryString()); |
| 195 | message.obj.protocolNeg.obj.type.value = NegotiationType.TYPE_RDP_NEG_REQ; |
| 196 | message.obj.protocolNeg.obj.result.value = this.requestedProtocol; |
| 197 | this.transport.send(message); |
| 198 | |
| 199 | // next state wait connection confirm packet |
| 200 | var self = this; |
| 201 | this.transport.once('data', function(s) { |
| 202 | self.recvConnectionConfirm(s); |
| 203 | }); |
| 204 | }; |
| 205 | |
| 206 | /** |
| 207 | * close stack |
| 208 | */ |
| 209 | Client.prototype.close = function() { |
| 210 | this.transport.close(); |
| 211 | }; |
| 212 | |
| 213 | /** |
| 214 | * Receive connection from server |
| 215 | * @param s {Stream} |
| 216 | */ |
| 217 | Client.prototype.recvConnectionConfirm = function(s) { |
| 218 | var message = serverConnectionConfirm().read(s); |
| 219 | |
| 220 | if (message.obj.protocolNeg.obj.type.value == NegotiationType.TYPE_RDP_NEG_FAILURE) { |
| 221 | this.emit('error', { err: 'NODE_RDP_PROTOCOL_X224_NEG_FAILURE', code: message.obj.protocolNeg.obj.result.value }); |
| 222 | return; |
| 223 | //throw new error.ProtocolError('NODE_RDP_PROTOCOL_X224_NEG_FAILURE', 'Failure code:' + message.obj.protocolNeg.obj.result.value + " (see https://msdn.microsoft.com/en-us/library/cc240507.aspx)"); |
| 224 | } |
| 225 | |
| 226 | if (message.obj.protocolNeg.obj.type.value == NegotiationType.TYPE_RDP_NEG_RSP) { |
| 227 | this.selectedProtocol = message.obj.protocolNeg.obj.result.value; |
| 228 | } |
| 229 | |
| 230 | if ([Protocols.PROTOCOL_HYBRID_EX].indexOf(this.selectedProtocol) !== -1) { |
| 231 | this.emit('error', 'NODE_RDP_PROTOCOL_X224_NLA_NOT_SUPPORTED'); |
| 232 | return; |
| 233 | //throw new error.ProtocolError('NODE_RDP_PROTOCOL_X224_NLA_NOT_SUPPORTED'); |
| 234 | } |
| 235 | |
| 236 | if (this.selectedProtocol == Protocols.PROTOCOL_RDP) { |
| 237 | log.debug("RDP standard security selected"); |
| 238 | return; |
| 239 | } |
| 240 | |
| 241 | if (this.selectedProtocol == Protocols.PROTOCOL_HYBRID) { |
| 242 | log.debug("NLA security layer selected"); |
| 243 | var self = this; |
| 244 | var transportEx = this.transport.transport; |
| 245 | this.transport.transport.startTLS(function () { |
| 246 | //console.log('TLS connected, start cssp_connect()'); |
| 247 | var NLA = require('./nla'); |
| 248 | self.nla = new NLA(transportEx, function () { self.nlaCompleted(); }, self.config.domain, self.config.userName, self.config.password); |
| 249 | self.nla.sendNegotiateMessage(); |
| 250 | }); |
| 251 | return; |
| 252 | } |
| 253 | |
| 254 | // finish connection sequence |
| 255 | var self = this; |
| 256 | this.transport.on('data', function(s) { |
| 257 | self.recvData(s); |
| 258 | }); |
| 259 | |
| 260 | if (this.selectedProtocol == Protocols.PROTOCOL_SSL) { |
| 261 | log.debug("SSL standard security selected"); |
| 262 | this.transport.transport.startTLS(function() { |
| 263 | self.emit('connect', self.selectedProtocol); |
| 264 | }); |
| 265 | return; |
| 266 | } |
| 267 | }; |
| 268 | |
| 269 | /** |
| 270 | * Called when NLA is completed |
| 271 | */ |
| 272 | Client.prototype.nlaCompleted = function () { |
| 273 | const self = this; |
| 274 | delete self.nla; |
| 275 | this.transport.on('data', function (s) { self.recvData(s); }); |
| 276 | this.emit('connect', this.selectedProtocol); |
| 277 | } |
| 278 | |
| 279 | |
| 280 | /** |
| 281 | * Server x224 automata |
| 282 | */ |
| 283 | function Server(transport, keyFilePath, crtFilePath) { |
| 284 | X224.call(this, transport); |
| 285 | this.keyFilePath = keyFilePath; |
| 286 | this.crtFilePath = crtFilePath; |
| 287 | var self = this; |
| 288 | this.transport.once('data', function (s) { |
| 289 | self.recvConnectionRequest(s); |
| 290 | }); |
| 291 | } |
| 292 | |
| 293 | //inherit from X224 automata |
| 294 | inherits(Server, X224); |
| 295 | |
| 296 | /** |
| 297 | * @see http://msdn.microsoft.com/en-us/library/cc240470.aspx |
| 298 | * @param s {type.Stream} |
| 299 | */ |
| 300 | Server.prototype.recvConnectionRequest = function (s) { |
| 301 | var request = clientConnectionRequestPDU().read(s); |
| 302 | if (!request.obj.protocolNeg.isReaded) { |
| 303 | throw new Error('NODE_RDP_PROTOCOL_X224_NO_BASIC_SECURITY_LAYER'); |
| 304 | } |
| 305 | |
| 306 | this.requestedProtocol = request.obj.protocolNeg.obj.result.value; |
| 307 | this.selectedProtocol = this.requestedProtocol & Protocols.PROTOCOL_SSL; |
| 308 | |
| 309 | if (!(this.selectedProtocol & Protocols.PROTOCOL_SSL)) { |
| 310 | var confirm = serverConnectionConfirm(); |
| 311 | confirm.obj.protocolNeg.obj.type.value = NegociationType.TYPE_RDP_NEG_FAILURE; |
| 312 | confirm.obj.protocolNeg.obj.result.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER; |
| 313 | this.transport.send(confirm); |
| 314 | this.close(); |
| 315 | } |
| 316 | else { |
| 317 | this.sendConnectionConfirm(); |
| 318 | } |
| 319 | }; |
| 320 | |
| 321 | /** |
| 322 | * Start SSL connection if needed |
| 323 | * @see http://msdn.microsoft.com/en-us/library/cc240501.aspx |
| 324 | */ |
| 325 | Server.prototype.sendConnectionConfirm = function () { |
| 326 | var confirm = serverConnectionConfirm(); |
| 327 | confirm.obj.protocolNeg.obj.type.value = NegotiationType.TYPE_RDP_NEG_RSP; |
| 328 | confirm.obj.protocolNeg.obj.result.value = this.selectedProtocol; |
| 329 | this.transport.send(confirm); |
| 330 | |
| 331 | // finish connection sequence |
| 332 | var self = this; |
| 333 | this.transport.on('data', function(s) { |
| 334 | self.recvData(s); |
| 335 | }); |
| 336 | |
| 337 | this.transport.transport.listenTLS(this.keyFilePath, this.crtFilePath, function() { |
| 338 | log.debug('start SSL connection'); |
| 339 | self.emit('connect', self.requestedProtocol); |
| 340 | }); |
| 341 | }; |
| 342 | |
| 343 | /** |
| 344 | * Module exports |
| 345 | */ |
| 346 | module.exports = { |
| 347 | Client : Client, |
| 348 | Server : Server |
| 349 | }; |