-
-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathSSHConnection.php
187 lines (148 loc) · 4.81 KB
/
SSHConnection.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<?php
namespace DivineOmega\SSHConnection;
use InvalidArgumentException;
use phpseclib\Crypt\RSA;
use phpseclib\Net\SCP;
use phpseclib\Net\SSH2;
use RuntimeException;
class SSHConnection
{
const FINGERPRINT_MD5 = 'md5';
const FINGERPRINT_SHA1 = 'sha1';
private $hostname;
private $port = 22;
private $username;
private $password;
private $privateKeyPath;
private $privateKeyContents;
private $timeout;
private $connected = false;
private $ssh;
public function to(string $hostname): self
{
$this->hostname = $hostname;
return $this;
}
public function onPort(int $port): self
{
$this->port = $port;
return $this;
}
public function as(string $username): self
{
$this->username = $username;
return $this;
}
public function withPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function withPrivateKey(string $privateKeyPath): self
{
$this->privateKeyPath = $privateKeyPath;
return $this;
}
public function withPrivateKeyString(string $privateKeyContents): self
{
$this->privateKeyContents = $privateKeyContents;
return $this;
}
public function timeout(int $timeout): self
{
$this->timeout = $timeout;
return $this;
}
private function sanityCheck()
{
if (!$this->hostname) {
throw new InvalidArgumentException('Hostname not specified.');
}
if (!$this->username) {
throw new InvalidArgumentException('Username not specified.');
}
if (!$this->password && !$this->privateKeyPath && !$this->privateKeyContents) {
throw new InvalidArgumentException('No password or private key specified.');
}
}
public function connect(): self
{
$this->sanityCheck();
$this->ssh = new SSH2($this->hostname, $this->port);
if (!$this->ssh) {
throw new RuntimeException('Error connecting to server.');
}
if ($this->privateKeyPath || $this->privateKeyContents) {
$key = new RSA();
if ($this->privateKeyPath) {
$key->loadKey(file_get_contents($this->privateKeyPath));
} else if ($this->privateKeyContents) {
$key->loadKey($this->privateKeyContents);
}
$authenticated = $this->ssh->login($this->username, $key);
if (!$authenticated) {
throw new RuntimeException('Error authenticating with public-private key pair.');
}
}
if ($this->password) {
$authenticated = $this->ssh->login($this->username, $this->password);
if (!$authenticated) {
throw new RuntimeException('Error authenticating with password.');
}
}
if ($this->timeout) {
$this->ssh->setTimeout($this->timeout);
}
$this->connected = true;
return $this;
}
public function disconnect(): void
{
if (!$this->connected) {
throw new RuntimeException('Unable to disconnect. Not yet connected.');
}
$this->ssh->disconnect();
}
public function run(string $command): SSHCommand
{
if (!$this->connected) {
throw new RuntimeException('Unable to run commands when not connected.');
}
return new SSHCommand($this->ssh, $command);
}
public function fingerprint(string $type = self::FINGERPRINT_MD5)
{
if (!$this->connected) {
throw new RuntimeException('Unable to get fingerprint when not connected.');
}
$hostKey = substr($this->ssh->getServerPublicHostKey(), 8);
switch ($type) {
case 'md5':
return strtoupper(md5($hostKey));
case 'sha1':
return strtoupper(sha1($hostKey));
}
throw new InvalidArgumentException('Invalid fingerprint type specified.');
}
public function upload(string $localPath, string $remotePath): bool
{
if (!$this->connected) {
throw new RuntimeException('Unable to upload file when not connected.');
}
if (!file_exists($localPath)) {
throw new InvalidArgumentException('The local file does not exist.');
}
return (new SCP($this->ssh))->put($remotePath, $localPath, SCP::SOURCE_LOCAL_FILE);
}
public function download(string $remotePath, string $localPath): bool
{
if (!$this->connected) {
throw new RuntimeException('Unable to download file when not connected.');
}
return (new SCP($this->ssh))->get($remotePath, $localPath);
}
public function isConnected(): bool
{
return $this->connected;
}
}