Skip to content

Commit 4f13224

Browse files
committed
Rework to use phpseclib
1 parent 1a19ccb commit 4f13224

File tree

3 files changed

+26
-55
lines changed

3 files changed

+26
-55
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
],
1212
"require": {
1313
"php": ">=7.1",
14-
"ext-ssh2": "*"
14+
"phpseclib/phpseclib": "^2.0"
1515
},
1616
"require-dev": {
1717
"phpunit/phpunit": "^7.0||^8.0",

src/SSHCommand.php

+7-39
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,32 @@
22

33
namespace DivineOmega\SSHConnection;
44

5+
use phpseclib\Net\SSH2;
56
use RuntimeException;
67

78
class SSHCommand
89
{
910
const EXECUTION_TIMEOUT_SECONDS = 30;
1011
const STREAM_BYTES_PER_READ = 4096;
1112

12-
private $resource;
13+
private $ssh;
1314
private $command;
1415
private $output;
1516
private $error;
1617

17-
public function __construct($resource, string $command)
18+
public function __construct(SSH2 $ssh, string $command)
1819
{
19-
$this->resource = $resource;
20+
$this->ssh = $ssh;
2021
$this->command = $command;
2122

2223
$this->execute();
2324
}
2425

2526
private function execute()
2627
{
27-
$stdout = ssh2_exec($this->resource, $this->command);
28-
29-
if (!$stdout) {
30-
throw new RuntimeException('Failed to execute command (no stdout stream): '.$this->command);
31-
}
32-
33-
$stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR);
34-
35-
if (!$stderr) {
36-
throw new RuntimeException('Failed to execute command (no stdout stream): '.$this->command);
37-
}
38-
39-
$this->readStreams($stdout, $stderr);
40-
}
41-
42-
private function readStreams($stdout, $stderr)
43-
{
44-
$startTime = time();
45-
46-
while (true) {
47-
$this->output = fread($stdout, self::STREAM_BYTES_PER_READ);
48-
$this->error = fread($stderr, self::STREAM_BYTES_PER_READ);
49-
50-
if (feof($stdout) && feof($stderr)) {
51-
break;
52-
}
53-
54-
if (time() - $startTime > self::EXECUTION_TIMEOUT_SECONDS) {
55-
throw new RuntimeException(
56-
'Command execution took over '.self::EXECUTION_TIMEOUT_SECONDS.' seconds: '.$this->command
57-
);
58-
}
59-
60-
// Prevent thrashing
61-
sleep(1);
62-
}
28+
$this->ssh->enableQuietMode();
29+
$this->output = $this->ssh->exec($this->command);
30+
$this->error = $this->ssh->getStdError();
6331
}
6432

6533
public function getRawOutput(): string

src/SSHConnection.php

+18-15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace DivineOmega\SSHConnection;
44

55
use InvalidArgumentException;
6+
use phpseclib\Crypt\RSA;
7+
use phpseclib\Net\SCP;
8+
use phpseclib\Net\SSH2;
69
use RuntimeException;
710

811
class SSHConnection
@@ -11,10 +14,9 @@ class SSHConnection
1114
private $port = 22;
1215
private $username;
1316
private $password;
14-
private $publicKeyPath;
1517
private $privateKeyPath;
1618
private $connected = false;
17-
private $resource;
19+
private $ssh;
1820

1921
public function to(string $hostname): self
2022
{
@@ -40,9 +42,8 @@ public function withPassword(string $password): self
4042
return $this;
4143
}
4244

43-
public function withKeyPair(string $publicKeyPath, string $privateKeyPath): self
45+
public function withPrivateKey(string $privateKeyPath): self
4446
{
45-
$this->publicKeyPath = $publicKeyPath;
4647
$this->privateKeyPath = $privateKeyPath;
4748
return $this;
4849
}
@@ -57,30 +58,32 @@ private function sanityCheck()
5758
throw new InvalidArgumentException('Username not specified.');
5859
}
5960

60-
if (!$this->password && (!$this->publicKeyPath || !$this->privateKeyPath)) {
61-
throw new InvalidArgumentException('No password or public-private key pair specified.');
61+
if (!$this->password && (!$this->privateKeyPath)) {
62+
throw new InvalidArgumentException('No password or private key path specified.');
6263
}
6364
}
6465

6566
public function connect(): self
6667
{
6768
$this->sanityCheck();
6869

69-
$this->resource = ssh2_connect($this->hostname, $this->port);
70+
$this->ssh = new SSH2($this->hostname);
7071

71-
if (!$this->resource) {
72+
if (!$this->ssh) {
7273
throw new RuntimeException('Error connecting to server.');
7374
}
7475

75-
if ($this->publicKeyPath || $this->privateKeyPath) {
76-
$authenticated = ssh2_auth_pubkey_file($this->resource, $this->username, $this->publicKeyPath, $this->privateKeyPath);
76+
if ($this->privateKeyPath) {
77+
$key = new RSA();
78+
$key->loadKey(file_get_contents($this->privateKeyPath));
79+
$authenticated = $this->ssh->login($this->username, $key);
7780
if (!$authenticated) {
7881
throw new RuntimeException('Error authenticating with public-private key pair.');
7982
}
8083
}
8184

8285
if ($this->password) {
83-
$authenticated = ssh2_auth_password($this->resource, $this->username, $this->password);
86+
$authenticated = $this->ssh->login($this->username, $this->password);
8487
if (!$authenticated) {
8588
throw new RuntimeException('Error authenticating with password.');
8689
}
@@ -97,7 +100,7 @@ public function disconnect(): void
97100
throw new RuntimeException('Unable to disconnect. Not yet connected.');
98101
}
99102

100-
ssh2_disconnect($this->resource);
103+
$this->ssh->disconnect();
101104
}
102105

103106
public function run(string $command): SSHCommand
@@ -106,7 +109,7 @@ public function run(string $command): SSHCommand
106109
throw new RuntimeException('Unable to run commands when not connected.');
107110
}
108111

109-
return new SSHCommand($this->resource, $command);
112+
return new SSHCommand($this->ssh, $command);
110113
}
111114

112115
public function upload(string $localPath, string $remotePath): bool
@@ -119,7 +122,7 @@ public function upload(string $localPath, string $remotePath): bool
119122
throw new InvalidArgumentException('The local file does not exist.');
120123
}
121124

122-
return ssh2_scp_send($this->resource, $localPath, $remotePath);
125+
return (new SCP($this->ssh))->put($remotePath, $localPath, SCP::SOURCE_LOCAL_FILE);
123126
}
124127

125128
public function download(string $remotePath, string $localPath): bool
@@ -128,7 +131,7 @@ public function download(string $remotePath, string $localPath): bool
128131
throw new RuntimeException('Unable to download file when not connected.');
129132
}
130133

131-
return ssh2_scp_recv($this->resource, $remotePath, $localPath);
134+
return (new SCP($this->ssh))->get($remotePath, $localPath);
132135
}
133136

134137
public function isConnected(): bool

0 commit comments

Comments
 (0)