Skip to content

Commit a54d304

Browse files
authored
Merge pull request #48 from clue-labs/openssh-update
Support OpenSSH 9.2+ for `SshSocksConnector`
2 parents e0b3d78 + 4338012 commit a54d304

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

‎src/SshSocksConnector.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ private function listen()
237237
// match everything related to our forwarding channel
238238
// forwarding success will only be reported to stderr via debug1 / -v
239239
// debug1: channel 0: new [port listener]
240-
if (\preg_match('/^(?:debug\d\: )channel \d: new \[port listener\]$/', $line)) {
240+
// debug1: channel 0: new port-listener [port listener] (inactive timeout: 0)
241+
if (\preg_match('/^(?:debug\d\: )channel \d: new(?: port-listener)? \[port listener\](?: \([^()]*\))?$/', $line)) {
241242
$deferred->resolve($process);
242243
}
243244
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Clue\Tests\React\SshProxy;
4+
5+
use Clue\React\SshProxy\SshSocksConnector;
6+
use React\EventLoop\Loop;
7+
use React\Promise\Deferred;
8+
9+
class IntegrationSshSocksConnectorTest extends TestCase
10+
{
11+
public static function providePortListenerMessage()
12+
{
13+
return array(
14+
'OpenSSH < 9.2' => array(
15+
'debug1: channel 0: new [port listener]'
16+
),
17+
'OpenSSH 9.2+' => array(
18+
'debug1: channel 0: new port-listener [port listener] (inactive timeout: 0)'
19+
),
20+
'OpenSSH 10.6+' => array(
21+
'debug1: channel 0: new port-listener [port listener] (inactive timeout: 0.000000)'
22+
)
23+
);
24+
}
25+
26+
/**
27+
* @dataProvider providePortListenerMessage
28+
* @param string $line
29+
*/
30+
public function testConnectWillInvokeSocksConnectorWhenProcessOutputsPortListenerMessage($line)
31+
{
32+
$connector = new SshSocksConnector('host');
33+
34+
$ref = new \ReflectionProperty($connector, 'cmd');
35+
if (PHP_VERSION_ID < 80100) {
36+
$ref->setAccessible(true);
37+
}
38+
$ref->setValue($connector, 'echo "' . $line . '" >&2; #');
39+
40+
$deferred = new Deferred();
41+
$socks = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
42+
$socks->expects($this->once())->method('connect')->with('example.com:80')->willReturn($deferred->promise());
43+
44+
$ref = new \ReflectionProperty($connector, 'socks');
45+
if (PHP_VERSION_ID < 80100) {
46+
$ref->setAccessible(true);
47+
}
48+
$ref->setValue($connector, $socks);
49+
50+
$connector->connect('example.com:80');
51+
52+
Loop::run();
53+
}
54+
55+
public function testConnectWillRejectWithExceptionAndNotInvokeSocksConnectorWhenProcessEndsWithoutPortListenerMessage()
56+
{
57+
$connector = new SshSocksConnector('host');
58+
59+
$ref = new \ReflectionProperty($connector, 'cmd');
60+
if (PHP_VERSION_ID < 80100) {
61+
$ref->setAccessible(true);
62+
}
63+
$ref->setValue($connector, 'echo "debug1: channel 1: new dynamic-tcpip [dynamic-tcpip] (inactive timeout: 0)" >&2; #');
64+
65+
$socks = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
66+
$socks->expects($this->never())->method('connect');
67+
68+
$ref = new \ReflectionProperty($connector, 'socks');
69+
if (PHP_VERSION_ID < 80100) {
70+
$ref->setAccessible(true);
71+
}
72+
$ref->setValue($connector, $socks);
73+
74+
$promise = $connector->connect('example.com:80');
75+
$promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
76+
77+
Loop::run();
78+
}
79+
}

0 commit comments

Comments
 (0)