Skip to content

Commit

Permalink
fix: Close channel correctly when receiving SSH_Message_Channel_Close
Browse files Browse the repository at this point in the history
Fixes #116

When receiving a SSH_Message_Channel_Close message, the channel was not
being properly closed. This caused the stdout stream to remain open,
preventing the drain() operation from completing.

The fix:
1. Let the channel handle the close message properly
2. Added test to verify channel close behavior
  • Loading branch information
cbenhagen committed Dec 14, 2024
1 parent bb8fb29 commit be8a401
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 1 deletion.
8 changes: 7 additions & 1 deletion lib/src/ssh_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'package:dartssh2/src/message/msg_userauth.dart';
import 'package:dartssh2/src/ssh_message.dart';
import 'package:dartssh2/src/socket/ssh_socket.dart';
import 'package:dartssh2/src/ssh_userauth.dart';
import 'package:meta/meta.dart';

/// https://datatracker.ietf.org/doc/html/rfc4252#section-8
typedef SSHPasswordRequestHandler = FutureOr<String?> Function();
Expand Down Expand Up @@ -486,6 +487,10 @@ class SSHClient {
}
}

/// Handles a raw SSH packet. This method is only exposed for testing purposes.
@visibleForTesting
void handlePacket(Uint8List packet) => _handlePacket(packet);

void _sendMessage(SSHMessage message) {
printTrace?.call('-> $socket: $message');
_transport.sendPacket(message.encode());
Expand Down Expand Up @@ -792,7 +797,8 @@ class SSHClient {
printTrace?.call('<- $socket: $message');
final channel = _channels[message.recipientChannel];
if (channel != null) {
channel.close();
channel.handleMessage(message);
//channel.close();
_channels.remove(message.recipientChannel);
_channelIdAllocator.release(message.recipientChannel);
}
Expand Down
30 changes: 30 additions & 0 deletions test/src/channel/ssh_channel_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:dartssh2/dartssh2.dart';
import 'package:test/test.dart';

import '../../test_utils.dart';

void main() {
group('SSHChannel', () {
late SSHClient client;
late SSHSession session;

setUp(() async {
client = await getTestClient();
await client.authenticated;
session = await client.shell();
});

tearDown(() {
client.close();
});

test('stdout stream handles remote channel close correctly', () async {
final drainFuture = session.stdout.drain<void>();

final closeMessage = createChannelCloseMessage(0);
client.handlePacket(closeMessage);

await drainFuture;
}, timeout: const Timeout(Duration(seconds: 1)));
});
}
10 changes: 10 additions & 0 deletions test/test_utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:io';
import 'dart:typed_data';

import 'package:dartssh2/dartssh2.dart';
import 'package:dartssh2/src/message/msg_channel.dart';

/// A honeypot that accepts all passwords and public-keys
Future<SSHClient> getHoneypotClient() async {
Expand Down Expand Up @@ -40,3 +42,11 @@ Future<List<SSHKeyPair>> getTestKeyPairs() async {
String fixture(String path) {
return File('test/fixtures/$path').readAsStringSync();
}

/// Create a [SSH_Message_Channel_Close] message.
Uint8List createChannelCloseMessage(int recipientChannel) {
final message = SSH_Message_Channel_Close(
recipientChannel: recipientChannel,
);
return message.encode();
}

0 comments on commit be8a401

Please sign in to comment.