UNPKG

@microsoft/dev-tunnels-ssh

Version:
388 lines 19.3 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var ConnectionService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectionService = void 0; const sshService_1 = require("./sshService"); const connectionMessages_1 = require("../messages/connectionMessages"); const promiseCompletionSource_1 = require("../util/promiseCompletionSource"); const sshChannel_1 = require("../sshChannel"); const cancellation_1 = require("../util/cancellation"); const errors_1 = require("../errors"); const sshChannelOpeningEventArgs_1 = require("../events/sshChannelOpeningEventArgs"); const serviceActivation_1 = require("./serviceActivation"); const trace_1 = require("../trace"); const sshExtendedDataEventArgs_1 = require("../events/sshExtendedDataEventArgs"); let ConnectionService = ConnectionService_1 = class ConnectionService extends sshService_1.SshService { constructor(session) { super(session); this.channelCounter = 0; this.channelMap = new Map(); this.nonAcceptedChannels = new Map(); this.pendingChannels = new Map(); this.pendingAcceptChannels = new Map(); } get channels() { return Array.from(this.channelMap.values()); } close(e) { let channelCompletions = [...this.pendingChannels.values()].map((pc) => pc.completionSource); if (this.pendingAcceptChannels.size > 0) { channelCompletions = channelCompletions.concat([...this.pendingAcceptChannels.values()].reduce((a, b) => a.concat(b))); } for (const channel of this.channelMap.values()) { channel.close(e); } for (const channelCompletion of channelCompletions) { channelCompletion.reject(e); } } dispose() { const channels = [...this.channelMap.values()]; let channelCompletions = [...this.pendingChannels.values()].map((pc) => pc.completionSource); if (this.pendingAcceptChannels.size > 0) { channelCompletions = channelCompletions.concat([...this.pendingAcceptChannels.values()].reduce((a, b) => a.concat(b))); } for (const channel of channels) { channel.dispose(); } for (const channelCompletion of channelCompletions) { channelCompletion.reject(new errors_1.ObjectDisposedError('Session closed.')); } super.dispose(); } async acceptChannel(channelType, cancellation) { const completionSource = new promiseCompletionSource_1.PromiseCompletionSource(); let cancellationRegistration; if (cancellation) { if (cancellation.isCancellationRequested) throw new cancellation_1.CancellationError(); cancellationRegistration = cancellation.onCancellationRequested(() => { const list = this.pendingAcceptChannels.get(channelType); if (list) { const index = list.findIndex((item) => Object.is(item, completionSource)); if (index >= 0) { list.splice(index, 1); } } completionSource.reject(new cancellation_1.CancellationError()); }); } let channel = null; channel = Array.from(this.nonAcceptedChannels.values()).find((c) => c.channelType === channelType) || null; if (channel) { // Found a channel that was already opened but not accepted. this.nonAcceptedChannels.delete(channel.channelId); } else { // Set up the completion source to wait for a channel of the requested type. let list = this.pendingAcceptChannels.get(channelType); if (!list) { list = []; this.pendingAcceptChannels.set(channelType, list); } list.push(completionSource); } try { return channel || (await completionSource.promise); } finally { if (cancellationRegistration) cancellationRegistration.dispose(); } } async openChannel(openMessage, completionSource, cancellation) { const channelId = ++this.channelCounter; openMessage.senderChannel = channelId; let cancellationRegistration = null; if (cancellation) { if (cancellation.isCancellationRequested) throw new cancellation_1.CancellationError(); cancellationRegistration = cancellation.onCancellationRequested(() => { if (this.pendingChannels.delete(channelId)) { completionSource.reject(new cancellation_1.CancellationError()); } }); } this.pendingChannels.set(channelId, { openMessage: openMessage, completionSource: completionSource, cancellationRegistration: cancellationRegistration, }); await this.session.sendMessage(openMessage); return channelId; } handleMessage(message, cancellation) { if (message instanceof connectionMessages_1.ChannelDataMessage) { return this.handleDataMessage(message); } else if (message instanceof connectionMessages_1.ChannelExtendedDataMessage) { return this.handleExtendedDataMessage(message); } else if (message instanceof connectionMessages_1.ChannelWindowAdjustMessage) { return this.handleAdjustWindowMessage(message); } else if (message instanceof connectionMessages_1.ChannelEofMessage) { return this.handleEofMessage(message); } else if (message instanceof connectionMessages_1.ChannelOpenMessage) { return this.handleOpenMessage(message, cancellation); } else if (message instanceof connectionMessages_1.ChannelCloseMessage) { return this.handleCloseMessage(message); } else if (message instanceof connectionMessages_1.ChannelOpenConfirmationMessage) { return this.handleOpenConfirmationMessage(message, cancellation); } else if (message instanceof connectionMessages_1.ChannelOpenFailureMessage) { return this.handleOpenFailureMessage(message); } else if (message instanceof connectionMessages_1.ChannelRequestMessage) { return this.handleRequestMessage(message, cancellation); } else if (message instanceof connectionMessages_1.ChannelSuccessMessage) { return this.handleSuccessMessage(message); } else if (message instanceof connectionMessages_1.ChannelFailureMessage) { return this.handleFailureMessage(message); } else { throw new Error(`Message not implemented: ${message}`); } } async handleOpenMessage(message, cancellation) { var _a; const senderChannel = message.senderChannel; if (!this.session.canAcceptRequests) { this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelOpenFailed, 'Channel open request blocked because the session is not yet authenticated.'); const openFailureMessage = new connectionMessages_1.ChannelOpenFailureMessage(); openFailureMessage.recipientChannel = senderChannel; openFailureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.administrativelyProhibited; openFailureMessage.description = 'Authenticate before opening channels.'; await this.session.sendMessage(openFailureMessage, cancellation); return; } else if (!message.channelType) { const openFailureMessage = new connectionMessages_1.ChannelOpenFailureMessage(); openFailureMessage.recipientChannel = senderChannel; openFailureMessage.reasonCode = connectionMessages_1.SshChannelOpenFailureReason.unknownChannelType; openFailureMessage.description = 'Channel type not specified.'; await this.session.sendMessage(openFailureMessage, cancellation); return; } // Save a copy of the message because its buffer will be overwitten by the next receive. message = message.convertTo(new connectionMessages_1.ChannelOpenMessage(), true); // The confirmation message may be reassigned if the opening task returns a custom message. let confirmationMessage = new connectionMessages_1.ChannelOpenConfirmationMessage(); const channelId = ++this.channelCounter; const channel = new sshChannel_1.SshChannel(this, message.channelType, channelId, senderChannel, message.maxWindowSize, message.maxPacketSize, message, confirmationMessage); let responseMessage; const args = new sshChannelOpeningEventArgs_1.SshChannelOpeningEventArgs(message, channel, true); try { await this.session.handleChannelOpening(args, cancellation); if (args.openingPromise) { responseMessage = await args.openingPromise; } else if (args.failureReason !== connectionMessages_1.SshChannelOpenFailureReason.none) { const failureMessage = new connectionMessages_1.ChannelOpenFailureMessage(); failureMessage.reasonCode = args.failureReason; failureMessage.description = (_a = args.failureDescription) !== null && _a !== void 0 ? _a : undefined; responseMessage = failureMessage; } else { responseMessage = confirmationMessage; } } catch (e) { channel.dispose(); throw e; } if (responseMessage instanceof connectionMessages_1.ChannelOpenFailureMessage) { responseMessage.recipientChannel = senderChannel; try { await this.session.sendMessage(responseMessage, cancellation); } finally { channel.dispose(); } return; } // The session might have been closed while opening the channel. if (this.session.isClosed) { channel.dispose(); return; } // Prevent any changes to the channel max window size after sending the value in the // open confirmation message. channel.isMaxWindowSizeLocked = true; this.channelMap.set(channel.channelId, channel); confirmationMessage = responseMessage; confirmationMessage.recipientChannel = channel.remoteChannelId; confirmationMessage.senderChannel = channel.channelId; confirmationMessage.maxWindowSize = channel.maxWindowSize; confirmationMessage.maxPacketSize = channel.maxPacketSize; confirmationMessage.rewrite(); channel.openConfirmationMessage = confirmationMessage; await this.session.sendMessage(confirmationMessage, cancellation); // Check if there are any accept operations waiting on this channel type. let accepted = false; const list = this.pendingAcceptChannels.get(channel.channelType); while (list && list.length > 0) { const acceptCompletionSource = list.shift(); acceptCompletionSource.resolve(channel); accepted = true; break; } if (!accepted) { this.nonAcceptedChannels.set(channel.channelId, channel); } this.onChannelOpenCompleted(channel.channelId, channel); channel.enableSending(); } handleCloseMessage(message) { const channel = this.findChannelById(message.recipientChannel); if (channel) { channel.handleClose(); } } async handleOpenConfirmationMessage(message, cancellation) { var _a; let completionSource = null; let openMessage; const pendingChannel = this.pendingChannels.get(message.recipientChannel); if (pendingChannel) { openMessage = pendingChannel.openMessage; completionSource = pendingChannel.completionSource; if (pendingChannel.cancellationRegistration) { pendingChannel.cancellationRegistration.dispose(); } this.pendingChannels.delete(message.recipientChannel); } else if (this.channelMap.has(message.recipientChannel)) { throw new Error('Duplicate channel ID.'); } else { throw new Error('Channel confirmation was not requested.'); } // Save a copy of the message because its buffer will be overwitten by the next receive. message = message.convertTo(new connectionMessages_1.ChannelOpenConfirmationMessage(), true); const channel = new sshChannel_1.SshChannel(this, openMessage.channelType || sshChannel_1.SshChannel.sessionChannelType, message.recipientChannel, message.senderChannel, message.maxWindowSize, message.maxPacketSize, openMessage, message); // Set the channel max window size property to match the value sent in the open message, // (if specified) and lock it to prevent any further changes. if (typeof openMessage.maxWindowSize === 'number') { channel.maxWindowSize = openMessage.maxWindowSize; } channel.isMaxWindowSizeLocked = true; this.channelMap.set(channel.channelId, channel); const args = new sshChannelOpeningEventArgs_1.SshChannelOpeningEventArgs(openMessage, channel, false); await this.session.handleChannelOpening(args, cancellation); if (completionSource) { if (args.failureReason === connectionMessages_1.SshChannelOpenFailureReason.none) { completionSource.resolve(channel); } else { completionSource.reject(new errors_1.SshChannelError((_a = args.failureDescription) !== null && _a !== void 0 ? _a : 'Channel open failure.', args.failureReason)); return; } } else { this.onChannelOpenCompleted(channel.channelId, channel); } channel.enableSending(); } handleOpenFailureMessage(message) { let completionSource = null; const pendingChannel = this.pendingChannels.get(message.recipientChannel); if (pendingChannel) { completionSource = pendingChannel.completionSource; if (pendingChannel.cancellationRegistration) { pendingChannel.cancellationRegistration.dispose(); } this.pendingChannels.delete(message.recipientChannel); } if (completionSource != null) { completionSource.reject(new errors_1.SshChannelError(message.description || 'Channel open rejected.', message.reasonCode)); } else { this.onChannelOpenCompleted(message.recipientChannel, null); } } async handleRequestMessage(message, cancellation) { const channel = this.tryGetChannelForMessage(message); if (!channel) return; await channel.handleRequest(message, cancellation); } handleSuccessMessage(message) { const channel = this.tryGetChannelForMessage(message); channel === null || channel === void 0 ? void 0 : channel.handleResponse(true); } handleFailureMessage(message) { const channel = this.tryGetChannelForMessage(message); channel === null || channel === void 0 ? void 0 : channel.handleResponse(false); } handleDataMessage(message) { const channel = this.tryGetChannelForMessage(message); channel === null || channel === void 0 ? void 0 : channel.handleDataReceived(message.data); } handleExtendedDataMessage(message) { const channel = this.tryGetChannelForMessage(message); channel === null || channel === void 0 ? void 0 : channel.handleExtendedDataReceived(new sshExtendedDataEventArgs_1.SshExtendedDataEventArgs(message.dataTypeCode, message.data)); } handleAdjustWindowMessage(message) { const channel = this.tryGetChannelForMessage(message); channel === null || channel === void 0 ? void 0 : channel.adjustRemoteWindow(message.bytesToAdd); } handleEofMessage(message) { const channel = this.findChannelById(message.recipientChannel); channel === null || channel === void 0 ? void 0 : channel.handleEof(); } onChannelOpenCompleted(channelId, channel) { if (channel) { this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.channelOpened, `${this.session} ChannelOpenCompleted(${channel})`); } else { this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelOpenFailed, `${this.session} ChannelOpenCompleted(${channelId} failed)`); } } /** * Gets the channel object based on the message `recipientChannel` property. * Logs a warning if the channel was not found. */ tryGetChannelForMessage(channelMessage) { const channel = this.findChannelById(channelMessage.recipientChannel); if (!channel) { const messageString = channelMessage instanceof connectionMessages_1.ChannelDataMessage ? 'channel data message' : channelMessage.toString(); this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.channelRequestFailed, `Invalid channel ID ${channelMessage.recipientChannel} in ${messageString}.`); } return channel; } findChannelById(id) { var _a; const channel = (_a = this.channelMap.get(id)) !== null && _a !== void 0 ? _a : null; return channel; } /* @internal */ removeChannel(channel) { this.channelMap.delete(channel.channelId); this.pendingChannels.delete(channel.channelId); } }; ConnectionService.serviceName = 'ssh-connection'; ConnectionService = ConnectionService_1 = __decorate([ (0, serviceActivation_1.serviceActivation)({ serviceRequest: ConnectionService_1.serviceName }) ], ConnectionService); exports.ConnectionService = ConnectionService; //# sourceMappingURL=connectionService.js.map