Blame view

sources/apps/files_external/lib/sftp.php 6.99 KB
03e52840d   Kload   Init
1
2
3
4
5
6
7
8
  <?php
  /**
   * Copyright (c) 2012 Henrik Kjölhede <hkjolhede@gmail.com>
   * This file is licensed under the Affero General Public License version 3 or
   * later.
   * See the COPYING-README file.
   */
  namespace OC\Files\Storage;
6d9380f96   Cédric Dupont   Update sources OC...
9
10
11
12
  /**
  * Uses phpseclib's Net_SFTP class and the Net_SFTP_Stream stream wrapper to
  * provide access to SFTP servers.
  */
03e52840d   Kload   Init
13
14
15
16
17
  class SFTP extends \OC\Files\Storage\Common {
  	private $host;
  	private $user;
  	private $password;
  	private $root;
6d9380f96   Cédric Dupont   Update sources OC...
18
19
20
  	/**
  	* @var \Net_SFTP
  	*/
03e52840d   Kload   Init
21
22
23
24
25
  	private $client;
  
  	private static $tempFiles = array();
  
  	public function __construct($params) {
6d9380f96   Cédric Dupont   Update sources OC...
26
27
28
29
30
31
32
33
34
35
  		// The sftp:// scheme has to be manually registered via inclusion of
  		// the 'Net/SFTP/Stream.php' file which registers the Net_SFTP_Stream
  		// stream wrapper as a side effect.
  		// A slightly better way to register the stream wrapper is available
  		// since phpseclib 0.3.7 in the form of a static call to
  		// Net_SFTP_Stream::register() which will trigger autoloading if
  		// necessary.
  		// TODO: Call Net_SFTP_Stream::register() instead when phpseclib is
  		//       updated to 0.3.7 or higher.
  		require_once 'Net/SFTP/Stream.php';
03e52840d   Kload   Init
36
37
38
39
40
41
42
  		$this->host = $params['host'];
  		$proto = strpos($this->host, '://');
  		if ($proto != false) {
  			$this->host = substr($this->host, $proto+3);
  		}
  		$this->user = $params['user'];
  		$this->password = $params['password'];
31b7f2792   Kload   Upgrade to ownclo...
43
44
  		$this->root
  			= isset($params['root']) ? $this->cleanPath($params['root']) : '/';
03e52840d   Kload   Init
45

31b7f2792   Kload   Upgrade to ownclo...
46
47
48
49
50
51
52
  		if ($this->root[0] != '/') {
  			 $this->root = '/' . $this->root;
  		}
  
  		if (substr($this->root, -1, 1) != '/') {
  			$this->root .= '/';
  		}
03e52840d   Kload   Init
53

31b7f2792   Kload   Upgrade to ownclo...
54
  		$hostKeys = $this->readHostKeys();
03e52840d   Kload   Init
55
  		$this->client = new \Net_SFTP($this->host);
31b7f2792   Kload   Upgrade to ownclo...
56

6d9380f96   Cédric Dupont   Update sources OC...
57
  		// The SSH Host Key MUST be verified before login().
31b7f2792   Kload   Upgrade to ownclo...
58
  		$currentHostKey = $this->client->getServerPublicHostKey();
31b7f2792   Kload   Upgrade to ownclo...
59
60
  		if (array_key_exists($this->host, $hostKeys)) {
  			if ($hostKeys[$this->host] != $currentHostKey) {
03e52840d   Kload   Init
61
62
63
  				throw new \Exception('Host public key does not match known key');
  			}
  		} else {
31b7f2792   Kload   Upgrade to ownclo...
64
65
  			$hostKeys[$this->host] = $currentHostKey;
  			$this->writeHostKeys($hostKeys);
03e52840d   Kload   Init
66
  		}
6d9380f96   Cédric Dupont   Update sources OC...
67
68
69
70
  
  		if (!$this->client->login($this->user, $this->password)) {
  			throw new \Exception('Login failed');
  		}
03e52840d   Kload   Init
71
72
73
  	}
  
  	public function test() {
31b7f2792   Kload   Upgrade to ownclo...
74
75
76
77
78
79
  		if (
  			!isset($this->host)
  			|| !isset($this->user)
  			|| !isset($this->password)
  		) {
  			return false;
03e52840d   Kload   Init
80
  		}
31b7f2792   Kload   Upgrade to ownclo...
81
  		return $this->client->nlist() !== false;
03e52840d   Kload   Init
82
83
84
85
86
  	}
  
  	public function getId(){
  		return 'sftp::' . $this->user . '@' . $this->host . '/' . $this->root;
  	}
6d9380f96   Cédric Dupont   Update sources OC...
87
88
89
  	/**
  	 * @param string $path
  	 */
31b7f2792   Kload   Upgrade to ownclo...
90
  	private function absPath($path) {
03e52840d   Kload   Init
91
92
  		return $this->root . $this->cleanPath($path);
  	}
31b7f2792   Kload   Upgrade to ownclo...
93
  	private function hostKeysPath() {
03e52840d   Kload   Init
94
95
96
97
98
  		try {
  			$storage_view = \OCP\Files::getStorage('files_external');
  			if ($storage_view) {
  				return \OCP\Config::getSystemValue('datadirectory') .
  					$storage_view->getAbsolutePath('') .
31b7f2792   Kload   Upgrade to ownclo...
99
  					'ssh_hostKeys';
03e52840d   Kload   Init
100
101
102
103
104
  			}
  		} catch (\Exception $e) {
  		}
  		return false;
  	}
31b7f2792   Kload   Upgrade to ownclo...
105
  	private function writeHostKeys($keys) {
03e52840d   Kload   Init
106
  		try {
31b7f2792   Kload   Upgrade to ownclo...
107
108
109
110
111
112
113
114
115
  			$keyPath = $this->hostKeysPath();
  			if ($keyPath && file_exists($keyPath)) {
  				$fp = fopen($keyPath, 'w');
  				foreach ($keys as $host => $key) {
  					fwrite($fp, $host . '::' . $key . "
  ");
  				}
  				fclose($fp);
  				return true;
03e52840d   Kload   Init
116
  			}
03e52840d   Kload   Init
117
  		} catch (\Exception $e) {
03e52840d   Kload   Init
118
  		}
31b7f2792   Kload   Upgrade to ownclo...
119
  		return false;
03e52840d   Kload   Init
120
  	}
31b7f2792   Kload   Upgrade to ownclo...
121
  	private function readHostKeys() {
03e52840d   Kload   Init
122
  		try {
31b7f2792   Kload   Upgrade to ownclo...
123
124
  			$keyPath = $this->hostKeysPath();
  			if (file_exists($keyPath)) {
03e52840d   Kload   Init
125
126
  				$hosts = array();
  				$keys = array();
31b7f2792   Kload   Upgrade to ownclo...
127
  				$lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
03e52840d   Kload   Init
128
129
  				if ($lines) {
  					foreach ($lines as $line) {
31b7f2792   Kload   Upgrade to ownclo...
130
131
132
133
  						$hostKeyArray = explode("::", $line, 2);
  						if (count($hostKeyArray) == 2) {
  							$hosts[] = $hostKeyArray[0];
  							$keys[] = $hostKeyArray[1];
03e52840d   Kload   Init
134
135
136
137
138
139
140
141
142
143
144
145
  						}
  					}
  					return array_combine($hosts, $keys);
  				}
  			}
  		} catch (\Exception $e) {
  		}
  		return array();
  	}
  
  	public function mkdir($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
146
  			return $this->client->mkdir($this->absPath($path));
03e52840d   Kload   Init
147
148
149
150
151
152
153
  		} catch (\Exception $e) {
  			return false;
  		}
  	}
  
  	public function rmdir($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
154
  			return $this->client->delete($this->absPath($path), true);
03e52840d   Kload   Init
155
156
157
158
159
160
161
  		} catch (\Exception $e) {
  			return false;
  		}
  	}
  
  	public function opendir($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
162
  			$list = $this->client->nlist($this->absPath($path));
03e52840d   Kload   Init
163
164
  
  			$id = md5('sftp:' . $path);
31b7f2792   Kload   Upgrade to ownclo...
165
  			$dirStream = array();
03e52840d   Kload   Init
166
167
  			foreach($list as $file) {
  				if ($file != '.' && $file != '..') {
31b7f2792   Kload   Upgrade to ownclo...
168
  					$dirStream[] = $file;
03e52840d   Kload   Init
169
170
  				}
  			}
31b7f2792   Kload   Upgrade to ownclo...
171
  			\OC\Files\Stream\Dir::register($id, $dirStream);
03e52840d   Kload   Init
172
173
174
175
176
177
178
179
  			return opendir('fakedir://' . $id);
  		} catch(\Exception $e) {
  			return false;
  		}
  	}
  
  	public function filetype($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
180
181
182
183
184
185
186
187
  			$stat = $this->client->stat($this->absPath($path));
  			if ($stat['type'] == NET_SFTP_TYPE_REGULAR) {
  				return 'file';
  			}
  
  			if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  				return 'dir';
  			}
03e52840d   Kload   Init
188
  		} catch (\Exeption $e) {
31b7f2792   Kload   Upgrade to ownclo...
189

03e52840d   Kload   Init
190
191
192
  		}
  		return false;
  	}
03e52840d   Kload   Init
193
194
  	public function file_exists($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
195
  			return $this->client->stat($this->absPath($path)) !== false;
03e52840d   Kload   Init
196
197
198
199
200
201
202
  		} catch (\Exception $e) {
  			return false;
  		}
  	}
  
  	public function unlink($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
203
  			return $this->client->delete($this->absPath($path), true);
03e52840d   Kload   Init
204
205
206
207
208
209
210
  		} catch (\Exception $e) {
  			return false;
  		}
  	}
  
  	public function fopen($path, $mode) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
211
  			$absPath = $this->absPath($path);
03e52840d   Kload   Init
212
213
214
  			switch($mode) {
  				case 'r':
  				case 'rb':
31b7f2792   Kload   Upgrade to ownclo...
215
216
217
  					if ( !$this->file_exists($path)) {
  						return false;
  					}
03e52840d   Kload   Init
218
219
220
221
222
223
224
225
226
227
228
229
  				case 'w':
  				case 'wb':
  				case 'a':
  				case 'ab':
  				case 'r+':
  				case 'w+':
  				case 'wb+':
  				case 'a+':
  				case 'x':
  				case 'x+':
  				case 'c':
  				case 'c+':
6d9380f96   Cédric Dupont   Update sources OC...
230
231
  					$context = stream_context_create(array('sftp' => array('session' => $this->client)));
  					return fopen($this->constructUrl($path), $mode, false, $context);
03e52840d   Kload   Init
232
233
234
235
236
  			}
  		} catch (\Exception $e) {
  		}
  		return false;
  	}
03e52840d   Kload   Init
237
238
  	public function touch($path, $mtime=null) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
239
240
241
  			if (!is_null($mtime)) {
  				return false;
  			}
03e52840d   Kload   Init
242
  			if (!$this->file_exists($path)) {
31b7f2792   Kload   Upgrade to ownclo...
243
  				$this->client->put($this->absPath($path), '');
03e52840d   Kload   Init
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
  			} else {
  				return false;
  			}
  		} catch (\Exception $e) {
  			return false;
  		}
  		return true;
  	}
  
  	public function getFile($path, $target) {
  		$this->client->get($path, $target);
  	}
  
  	public function uploadFile($path, $target) {
  		$this->client->put($target, $path, NET_SFTP_LOCAL_FILE);
  	}
  
  	public function rename($source, $target) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
263
264
265
266
267
268
269
  			if (!$this->is_dir($target) && $this->file_exists($target)) {
  				$this->unlink($target);
  			}
  			return $this->client->rename(
  				$this->absPath($source),
  				$this->absPath($target)
  			);
03e52840d   Kload   Init
270
271
272
273
274
275
276
  		} catch (\Exception $e) {
  			return false;
  		}
  	}
  
  	public function stat($path) {
  		try {
31b7f2792   Kload   Upgrade to ownclo...
277
  			$stat = $this->client->stat($this->absPath($path));
03e52840d   Kload   Init
278
279
280
281
282
283
284
285
  
  			$mtime = $stat ? $stat['mtime'] : -1;
  			$size = $stat ? $stat['size'] : 0;
  
  			return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1);
  		} catch (\Exception $e) {
  			return false;
  		}
03e52840d   Kload   Init
286
  	}
6d9380f96   Cédric Dupont   Update sources OC...
287
288
289
290
291
292
293
294
295
296
297
  
  	/**
  	 * @param string $path
  	 */
  	public function constructUrl($path) {
  		// Do not pass the password here. We want to use the Net_SFTP object
  		// supplied via stream context or fail. We only supply username and
  		// hostname because this might show up in logs (they are not used).
  		$url = 'sftp://'.$this->user.'@'.$this->host.$this->root.$path;
  		return $url;
  	}
03e52840d   Kload   Init
298
  }