PluginProbe
InfiniteWP Client / trunk
InfiniteWP Client vtrunk
1.13.10 1.13.7 trunk 0.1.4 0.1.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.11.0 1.11.1 1.12.1 1.12.3 All 92 releases
iwp-client / lib / sftp.php

sftp.php in InfiniteWP Client trunk, at lib/sftp.php

837 lines 30.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined('ABSPATH') )
4 die();
5
6 if (!class_exists('IWP_MMB_RemoteStorage_Extension')) require_once($GLOBALS['iwp_mmb_plugin_dir'].'/lib/sftp-extensions.php');
7
8 // Do not instantiate the storage object (as that is instantiated on demand), but only the helper
9 $IWP_addons_sftp = new IWP_MMB_RemoteStorage_sftp_helper;
10 class IWP_MMB_RemoteStorage_sftp_helper {
11
12 public function __construct() {
13 add_filter('IWP_sftp_ftps_notice', array($this, 'ftps_notice'));
14 add_filter('IWP_ftp_possible', array($this, 'IWP_ftp_possible'));
15 }
16
17 public function IWP_ftp_possible($funcs_disabled) {
18 if (!is_array($funcs_disabled)) return $funcs_disabled;
19 foreach (array('ftp_ssl_connect', 'ftp_login') as $func) {
20 if (!function_exists($func)) $funcs_disabled['ftpsslexplicit'][] = $func;
21 }
22 foreach (array('curl_exec') as $func) {
23 if (!function_exists($func)) $funcs_disabled['ftpsslimplicit'][] = $func;
24 }
25 return $funcs_disabled;
26 }
27
28 public function ftps_notice() {
29 return __("Encrypted FTP is available, and will be automatically tried first (before falling back to non-encrypted if it is not successful), unless you disable it using the expert options. The 'Test FTP Login' button will tell you what type of connection is in use.", 'InfiniteWP').' '.__('Some servers advertise encrypted FTP as available, but then time-out (after a long time) when you attempt to use it. If you find this happenning, then go into the "Expert Options" (below) and turn off SSL there.', 'InfiniteWP').' '.__('Explicit encryption is used by default. To force implicit encryption (port 990), add :990 to your FTP server below.', ' InfiniteWP');
30 }
31 }
32 #[AllowDynamicProperties]
33 class IWP_MMB_RemoteStorage_sftp extends IWP_MMB_RemoteStorage_Extension {
34
35 public function do_connect_and_chdir() {
36 $options = $this->get_options();
37 if (!array($options)) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'InfiniteWP'), 'SCP/SFTP'));
38
39 if (empty($options['host'])) return new WP_Error('no_settings', sprintf(__('No %s found', 'InfiniteWP'), __('SCP/SFTP host setting', 'InfiniteWP')));
40 if (empty($options['user'])) return new WP_Error('no_settings', sprintf(__('No %s found', 'InfiniteWP'), __('SCP/SFTP user setting', 'InfiniteWP')));
41 if (empty($options['pass']) && empty($options['key'])) return new WP_Error('no_settings', sprintf(__('No %s found', 'InfiniteWP'), __('SCP/SFTP password/key', 'InfiniteWP')));
42 $host = $options['host'];
43 $user = $options['user'];
44 $pass = (empty($options['pass'])) ? '' : $options['pass'];
45 $key = (empty($options['key'])) ? '' : $options['key'];
46 $port = empty($options['port']) ? 22 : (int) $options['port'];
47 $path = empty($options['path']) ? '' : $options['path'];
48 $scp = empty($options['scp']) ? 0 : 1;
49 if (!empty($options['ftp_site_folder'])) {
50 $site_name = iwp_getSiteName();
51 $path = trailingslashit($path);
52 $path.= trailingslashit($site_name);
53 }
54 $this->path = $path;
55
56 $fingerprint = '';
57
58 $sftp = $this->connect($host, $port, $fingerprint, $user, $pass, $key, $scp);
59 if (is_wp_error($sftp)) return $sftp;
60
61 // So far, so good
62 if ($path) {
63 if ($scp) {
64 // May fail - e.g. if directory already exists, or if the remote shell is restricted
65 @$this->ssh->exec("mkdir ".escapeshellarg($path));
66 // N.B. - have not changed directory (since cd may not be an available command)
67 } else {
68 @$sftp->mkdir($path);
69 // See if the directory now exists
70 if (!$sftp->chdir($path)) {
71 @$sftp->disconnect();
72 return new WP_Error('nochdir', __("Check your file permissions: Could not successfully create and enter directory:", 'InfiniteWP')." $path");
73 }
74 }
75 }
76
77 return $sftp;
78
79 }
80
81 public function upload_files($ret, $backup_array) {
82
83 global $iwp_backup_core, $IWP_backup;
84 $sftp = $this->do_connect_and_chdir();
85 if (is_wp_error($sftp)) {
86 foreach ($sftp->get_error_messages() as $key => $msg) {
87 $iwp_backup_core->log($msg);
88 $iwp_backup_core->log($msg, 'error');
89 }
90 return false;
91 }
92
93 if (empty($this->scp)) {
94 $iwp_backup_core->log("SFTP: Successfully logged in");
95 } else {
96 $iwp_backup_core->log("SCP: Successfully logged in");
97 }
98
99 $any_failures = false;
100
101 $iwp_backup_dir = $iwp_backup_core->backups_dir_location().'/';
102
103 foreach ($backup_array as $file) {
104 $iwp_backup_core->log("SCP/SFTP upload: attempt: $file");
105
106 $this->sftp_path = $iwp_backup_dir.'/'.$file;
107 $this->sftp_size = max(filesize($iwp_backup_dir.'/'.$file), 1);
108
109 if (empty($this->scp)) {
110
111 // SFTP
112
113 $this->sftp_began_at = 0;
114
115 try {
116 $remote_stat = $sftp->stat($file);
117 $current_remote_size = (is_array($remote_stat) && isset($remote_stat['size']) && $remote_stat['size'] > 0) ? $remote_stat['size'] : 0;
118 if ($current_remote_size > 0) {
119 $this->sftp_began_at = $current_remote_size;
120 $iwp_backup_core->log('SFTP: File exists remotely; upload will resume; remote size is: '.round($current_remote_size/1024, 2).' KB');
121 }
122 } catch (Exception $e) {
123 $iwp_backup_core->log('Exception when stating remote file ('.get_class($e).'): '.$e->getMessage());
124 $current_remote_size = 0;
125 }
126
127 if ($current_remote_size >= $this->sftp_size || $sftp->put($file, $iwp_backup_dir.'/'.$file, NET_SFTP_LOCAL_FILE, $current_remote_size, $current_remote_size, array($this, 'sftp_progress_callback'))) {
128 $iwp_backup_core->uploaded_file($file);
129 } else {
130 $any_failures = true;
131 $iwp_backup_core->log('ERROR: SFTP: Failed to upload file: '.$file);
132 $iwp_backup_core->log(sprintf(__('%s Error: Failed to upload', 'InfiniteWP'), 'SFTP').": $file", 'error');
133 }
134 } else {
135
136 // SCP
137
138 $rfile = empty($this->path) ? $file : trailingslashit($this->path).$file;
139 if ($sftp->put($rfile, $iwp_backup_dir.'/'.$file, NET_SCP_LOCAL_FILE, array($this, 'sftp_progress_callback'))) {
140 $iwp_backup_core->uploaded_file($file);
141 } else {
142 $any_failures = true;
143 $iwp_backup_core->log('ERROR: SCP: Failed to upload file: '.$file);
144 $iwp_backup_core->log(sprintf(__('%s Error: Failed to upload', 'InfiniteWP'), 'SCP').": $file", 'error');
145 }
146 }
147 }
148
149 // if (empty($this->scp)) {
150 // @$sftp->disconnect();
151 // } else {
152 // @$this->ssh->disconnect();
153 // }
154
155 if (!$any_failures) {
156 return array('sftp_object' => $sftp);
157 } else {
158 return null;
159 }
160
161 }
162
163 public function sftp_progress_callback($sent) {
164 global $iwp_backup_core;
165 static $sftp_last_logged_at = 0;
166 $bytes_sent = empty($this->sftp_began_at) ? $sent : $this->sftp_began_at + $sent;
167 if ($bytes_sent > $sftp_last_logged_at + 1048576) {
168 $perc = empty($this->sftp_size) ? 0 : round(100*$bytes_sent / $this->sftp_size, 1);
169 $iwp_backup_core->record_uploaded_chunk($perc, '', $this->sftp_path);
170 $sftp_last_logged_at = $bytes_sent;
171 }
172 }
173
174 public function delete_files($ret, $files, $sftp_arr = false) {
175
176 global $iwp_backup_core;
177 if (is_string($files)) $files = array($files);
178
179 if ($sftp_arr) {
180 $sftp = $sftp_arr['sftp_object'];
181 } else {
182 $sftp = $this->do_connect_and_chdir();
183 if (is_wp_error($sftp)) {
184 foreach ($sftp->get_error_messages() as $key => $msg) {
185 $iwp_backup_core->log($msg);
186 $iwp_backup_core->log($msg, 'error');
187 }
188 return false;
189 }
190 }
191
192 $some_success = false;
193
194 foreach ($files as $file) {
195
196 if (empty($this->scp)) {
197 $iwp_backup_core->log("SFTP: Delete remote: $file");
198 } else {
199 $iwp_backup_core->log("SCP: Delete remote: $file");
200 }
201
202 if (empty($this->scp)) {
203 if (!$sftp->delete($file, false)) {
204 $iwp_backup_core->log("SFTP: Delete failed: $file");
205 } else {
206 $some_success = true;
207 }
208 } else {
209 $rfile = empty($this->path) ? $file : trailingslashit($this->path).$file;
210 if (!$this->ssh->exec("rm -f ".escapeshellarg($rfile))) {
211 $iwp_backup_core->log("SCP: Delete failed: $rfile");
212 } else {
213 $some_success = true;
214 }
215 }
216 }
217
218 return $some_success;
219
220 }
221
222 public function listfiles($match = 'backup_') {
223 $sftp = $this->do_connect_and_chdir();
224 if (is_wp_error($sftp)) return $sftp;
225
226 $results = array();
227
228 if ($this->scp) {
229
230 $cdcom = empty($this->path) ? '' : "cd ".trailingslashit($this->path)." && ";
231
232 if (false == ($exec = $this->ssh->exec($cdcom."ls -l ".$match."*"))) {
233 $nosizes = true;
234 $exec = $this->ssh->exec($cdcom."ls -1 ".$match."*");
235 }
236 if (false != $exec) {
237 foreach (explode("\n", $exec) as $str) {
238 if ($nosizes) {
239 if (0 === strpos($str, $match)) $results[] = array('name' => $str);
240 } elseif (!$nosizes && preg_match('/^[^dls].*\s(\d+)\s+\S+\s+\d+\s+([:0-9]+)\s+'.$match.'(.*)$/', $str, $matches)) {
241 $results[] = array('name' => $match.$matches[3], 'size' => $matches[1]);
242 }
243 }
244 }
245
246 } else {
247 $dirlist = $sftp->rawlist();
248 if (!is_array($dirlist)) return array();
249
250 foreach ($dirlist as $path => $stat) {
251 if (0 === strpos($path, $match)) $results[] = array('name' => $path, 'size' => $stat['size']);
252 unset($dirlist[$path]);
253 }
254
255 }
256
257 return $results;
258 }
259
260 public function download_file($ret, $file) {
261
262 global $iwp_backup_core;
263
264 $sftp = $this->do_connect_and_chdir();
265 if (is_wp_error($sftp)) {
266 foreach ($sftp->get_error_messages() as $key => $msg) {
267 $iwp_backup_core->log($msg);
268 $iwp_backup_core->log($msg, 'error');
269 }
270 return false;
271 }
272
273 $fullpath = $iwp_backup_core->backups_dir_location().'/'.$file;
274
275 $rfile = (empty($this->scp) || empty($this->path)) ? $file : trailingslashit($this->path).$file;
276 if (!$sftp->get($rfile, $fullpath)) {
277 $iwp_backup_core->log("SFTP Error: Failed to download: $rfile");
278 $iwp_backup_core->log(sprintf(__('%s Error: Failed to download', 'InfiniteWP'), 'SCP/SFTP').": $rfile", 'error');
279 return false;
280 }
281 return true;
282 }
283
284 /**
285 * Open a connection to the SSH server
286 *
287 * @param String $host - SSH server hostname
288 * @param Integer $port - TCP port to connect to
289 * @param String $fingerprint - fingerprint to check (not currently implemented)
290 * @param String $user - login username
291 * @param String $pass - login password
292 * @param String $key - RSA private key to use for logging in (an alternative to password)
293 * @param Boolean $scp - if set, then SCP will be used; otherwise SFTP
294 * @param Boolean $debug - debugging mode: will ask phpseclib to log (which, being controlled by constants, may not be possible if they are already set)
295 * @return WP_Error|Net_SSH2|Net_SCP
296 */
297 public function connect($host, $port = 22, $fingerprint='', $user='', $pass = '', $key = '', $scp = false, $debug = false) {
298
299 global $iwp_backup_core;
300 $this->scp = $scp;
301
302 $timeout = (defined('IWP_SFTP_TIMEOUT') && is_numeric(IWP_SFTP_TIMEOUT)) ? IWP_SFTP_TIMEOUT : 15;
303
304 if ($scp) {
305 $iwp_backup_core->ensure_phpseclib('Net_SSH2', 'Net/SSH2');
306 $iwp_backup_core->ensure_phpseclib('Net_SCP', 'Net/SCP');
307 } else {
308 $iwp_backup_core->ensure_phpseclib('Net_SFTP', 'Net/SFTP');
309 }
310
311 // N.B. The same NET_SFTP_* constants exist; but as this point, we're only testing login, so will stick with SSH2
312 if ($debug) {
313 if (!defined('NET_SSH2_LOGGING')) {
314 // Alternative: NET_SFTP_LOG_SIMPLE. phpseclib source says that NET_SSH2_LOG_COMPLEX is most useful for SSH2
315 define('NET_SSH2_LOGGING', NET_SSH2_LOG_COMPLEX);
316 } elseif (NET_SSH2_LOGGING != NET_SSH2_LOG_COMPLEX) {
317 $iwp_backup_core->log("NET_SSH2_LOGGING: constant was already set; value not as desired (value=".NET_SSH2_LOGGING.", desired=".NET_SSH2_LOG_COMPLEX.")");
318 }
319 }
320
321 if ($scp) {
322 $this->ssh = new Net_SSH2($host, $port, $timeout);
323 } else {
324 $this->ssh = new Net_SFTP($host, $port, $timeout);
325 }
326
327 if (!empty($key)) {
328 $iwp_backup_core->ensure_phpseclib('Crypt_RSA', 'Crypt/RSA');
329 $iwp_backup_core->ensure_phpseclib('Math_BigInteger', 'Math/BigInteger');
330 $rsa = new Crypt_RSA();
331 if (false === $rsa->loadKey($key)) {
332 if (empty($pass)) return new WP_Error('no_load_key', __('The key provided was not in a valid format, or was corrupt.', 'InfiniteWP'));
333 } else {
334 $pass = $rsa;
335 }
336 }
337
338 // Ensure phpseclib Crypt_Blowfish is loaded, over PEAR's
339 $iwp_backup_core->ensure_phpseclib('Crypt_Blowfish', 'Crypt/Blowfish');
340 if (!$this->ssh->login($user, $pass)) {
341 $error_data = null;
342 $message = 'SSH 2 login failed';
343 if ($debug) {
344 $error_data = array(__("InfiniteWP debug mode is on: detailed debugging data follows.\n", 'InfiniteWP'));
345 $errors = $this->ssh->getErrors();
346 if (is_array($errors)) $error_data = array_merge($error_data, $errors);
347 // "Returns a string if NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX, an array if NET_SSH2_LOGGING == NET_SSH2_LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')"
348 $ssh_log = $this->ssh->getLog();
349 if (is_string($ssh_log)) {
350 $error_data[] = $ssh_log;
351 } elseif (is_array($ssh_log)) {
352 $error_data = array_merge($error_data, $ssh_log);
353 }
354 }
355 return new WP_Error('ssh2_nologin', $message, $error_data);
356 }
357
358 // if ($fingerprint) {
359 // $fingerprint = str_replace(':', '', $fingerprint);
360 // Fingerprint checking not yet supported by phpseclib
361 // return new WP_Error('debug', "Remove fingerprint: $remote_finger");
362 // }
363
364 return $scp ? new Net_SCP($this->ssh) : $this->ssh;
365
366 }
367
368 public function get_supported_features() {
369 // This options format is handled via only accessing options via $this->get_options()
370 return array('multi_options');
371 }
372
373 public function get_default_options() {
374 return array(
375 'host' => '',
376 'port' => '22',
377 'user' => '',
378 'pass' => '',
379 'key' => '',
380 'path' => '',
381 'scp' => 0,
382 );
383 }
384
385 public function config_print() {
386
387 $options = $this->get_options();
388 $host = isset($options['host']) ? htmlspecialchars($options['host']) : '';
389 $user = isset($options['user']) ? htmlspecialchars($options['user']) : '';
390 $pass = isset($options['pass']) ? htmlspecialchars($options['pass']) : '';
391 $key = isset($options['key']) ? htmlspecialchars($options['key']) : '';
392 $port = isset($options['port']) ? htmlspecialchars($options['port']) : 22;
393 $path = isset($options['path']) ? htmlspecialchars($options['path']) : '';
394 $scp = (isset($options['scp']) && $options['scp']) ? true : false;
395 $fingerprint = isset($options['fingerprint']) ? htmlspecialchars($options['fingerprint']) : '';
396
397 }
398
399 }
400
401 /**
402 * Adapted from http://www.solutionbot.com/2009/01/02/php-ftp-class/
403 *
404 * Our main tweaks to this class are to enable SSL with fallback for explicit encryption, and to provide rudimentary implicit support (the support for implicit is via Curl (since PHP's functions do not support it), and only extends to methods that we know we use).
405 *
406 * We somewhat crudely detect the request for implicit via use of port 990. But in the real world, it's unlikely we'll come across anything else - if we do, we can abstract a little more.
407 */
408 if (class_exists('IWP_MMB_ftp_wrapper')) {
409 class IWP_MMB_ftp_wrapper {
410
411 private $conn_id;
412
413 private $host;
414
415 private $username;
416
417 private $password;
418
419 private $port;
420
421 public $timeout = 60;
422
423 public $passive = true;
424
425 // Whether to *allow* (not necessarily require) SSL
426 public $ssl = true;
427
428 public $system_type = '';
429
430 public $login_type = 'non-encrypted';
431
432 public $use_server_certs = false;
433
434 public $disable_verify = true;
435
436 public $curl_handle;
437
438 public function __construct($host, $username, $password, $port = 21) {
439 $this->host = $host;
440 $this->username = $username;
441 $this->password = $password;
442 $this->port = $port;
443 }
444
445 public function connect() {
446
447 // Implicit SSL - not handled via PHP's native ftp_ functions, so we use curl instead
448 if (990 == $this->port || (defined('IWP_FTP_USECURL') && IWP_FTP_USECURL)) {
449 if (false == $this->ssl) {
450 $this->port = 21;
451 } else {
452 $this->curl_handle = curl_init();
453 if (!$this->curl_handle) {
454 $this->port = 21;
455 } else {
456 $options = array(
457 CURLOPT_USERPWD => $this->username . ':' . $this->password,
458 CURLOPT_PORT => $this->port,
459 CURLOPT_CONNECTTIMEOUT => 20,
460 // CURLOPT_TIMEOUT timeout is not just a "no-activity" timeout, but a total time limit on any Curl operation - undesirable
461 // CURLOPT_TIMEOUT => 20,
462 CURLOPT_FTP_CREATE_MISSING_DIRS => true
463 );
464 $options[CURLOPT_FTP_SSL] = CURLFTPSSL_TRY; // CURLFTPSSL_ALL, // require SSL For both control and data connections
465 if (990 == $this->port) {
466 $options[CURLOPT_FTPSSLAUTH] = CURLFTPAUTH_SSL; // CURLFTPAUTH_DEFAULT, // let cURL choose the FTP authentication method (either SSL or TLS)
467 } else {
468 $options[CURLOPT_FTPSSLAUTH] = CURLFTPAUTH_DEFAULT; // let cURL choose the FTP authentication method (either SSL or TLS)
469 }
470 // Prints to STDERR by default - noisy
471 if (defined('WP_DEBUG') && WP_DEBUG==true && IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_debug_mode')) {
472 $options[CURLOPT_VERBOSE] = true;
473 }
474 if ($this->disable_verify) {
475 $options[CURLOPT_SSL_VERIFYPEER] = false;
476 $options[CURLOPT_SSL_VERIFYHOST] = 0;
477 } else {
478 $options[CURLOPT_SSL_VERIFYPEER] = true;
479 }
480 if (!$this->use_server_certs) {
481 $options[CURLOPT_CAINFO] = $GLOBALS['iwp_mmb_plugin_dir'].'/lib/cacert.pem';
482 }
483 if (true != $this->passive) $options[CURLOPT_FTPPORT] = '-';
484 foreach ($options as $option_name => $option_value) {
485 if (!curl_setopt($this->curl_handle, $option_name, $option_value)) {
486 // throw new Exception(sprintf('Could not set cURL option: %s', $option_name));
487 global $iwp_backup_core;
488 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) {
489 $iwp_backup_core->log("Curl exception: will revert to normal FTP");
490 }
491 $this->port = 21;
492 $this->curl_handle = false;
493 }
494 }
495 }
496 // All done - leave
497 if ($this->curl_handle) {
498 $this->login_type = 'encrypted (implicit, port 990)';
499 return true;
500 }
501 }
502 }
503
504 $time_start = time();
505 if (function_exists('ftp_ssl_connect') && false !== $this->ssl) {
506 $this->conn_id = ftp_ssl_connect($this->host, $this->port, 15);
507 $attempting_ssl = true;
508 }
509
510 if ($this->conn_id) {
511 $this->login_type = 'encrypted';
512 $this->ssl = true;
513 } else {
514 $this->conn_id = ftp_connect($this->host, $this->port, 15);
515 }
516
517 if ($this->conn_id) $result = ftp_login($this->conn_id, $this->username, $this->password);
518
519 if (!empty($result)) {
520 ftp_set_option($this->conn_id, FTP_TIMEOUT_SEC, $this->timeout);
521 ftp_pasv($this->conn_id, $this->passive);
522 $this->system_type = ftp_systype($this->conn_id);
523 return true;
524 } elseif (!empty($attempting_ssl)) {
525 global $IWP_admin;
526 if (isset($IWP_admin->logged) && is_array($IWP_admin->logged)) {
527 // Clear the previous PHP messages, so that we only show the user messages from the method that worked (or from both if both fail)
528 $save_array = $IWP_admin->logged;
529 $IWP_admin->logged = array();
530 // trigger_error(__('Encrypted login failed; trying non-encrypted', 'IWP'), E_USER_NOTICE);
531 }
532 $this->ssl = false;
533 $this->login_type = 'non-encrypted';
534 $time_start = time();
535 $this->conn_id = ftp_connect($this->host, $this->port, 15);
536 if ($this->conn_id) $result = ftp_login($this->conn_id, $this->username, $this->password);
537 if (!empty($result)) {
538 ftp_set_option($this->conn_id, FTP_TIMEOUT_SEC, $this->timeout);
539 ftp_pasv($this->conn_id, $this->passive);
540 $this->system_type = ftp_systype($this->conn_id);
541 return true;
542 } else {
543 // Add back the previous PHP messages
544 if (isset($save_array)) $IWP_admin->logged = array_merge($save_array, $IWP_admin->logged);
545 }
546 }
547
548 // If we got here, then we failed
549 if (time() - $time_start > 14) {
550 global $IWP_admin;
551 if (isset($IWP_admin->logged) && is_array($IWP_admin->logged)) {
552 $IWP_admin->logged[] = sprintf(__('The %s connection timed out; if you entered the server correctly, then this is usually caused by a firewall blocking the connection - you should check with your web hosting company.', 'IWP'), 'FTP');
553 } else {
554 global $iwp_backup_core;
555 $iwp_backup_core->log(sprintf(__('The %s connection timed out; if you entered the server correctly, then this is usually caused by a firewall blocking the connection - you should check with your web hosting company.', 'InfiniteWP'), 'FTP'), 'error');
556 }
557 }
558
559 return false;
560
561 }
562
563 function curl_progress_function($download_size, $downloaded_size, $upload_size, $uploaded_size) {
564
565 if ($uploaded_size<1) return;
566
567 global $iwp_backup_core;
568
569 $percent = 100*($uploaded_size+$this->upload_from)/$this->upload_size;
570
571 // Log every megabyte or at least every 20%
572 if ($percent > $this->upload_last_recorded_percent + 20 || $uploaded_size > $this->uploaded_bytes + 1048576) {
573 $iwp_backup_core->record_uploaded_chunk(round($percent, 1), '', $this->upload_local_path);
574 $this->upload_last_recorded_percent=floor($percent);
575 $this->uploaded_bytes = $uploaded_size;
576 }
577
578 }
579
580 public function put($local_file_path, $remote_file_path, $mode = FTP_BINARY, $resume = false, $iwp_backup_core = false) {
581
582 $file_size = filesize($local_file_path);
583
584 $existing_size = 0;
585 if ($resume) {
586
587 if ($this->curl_handle) {
588 if (true === $this->curl_handle) $this->connect();
589 curl_setopt($this->curl_handle, CURLOPT_URL, 'ftps://'.$this->host.'/'.$remote_file_path);
590 curl_setopt($this->curl_handle, CURLOPT_NOBODY, true);
591 curl_setopt($this->curl_handle, CURLOPT_HEADER, false);
592
593 // curl_setopt($this->curl_handle, CURLOPT_FORBID_REUSE, true);
594
595 $getsize = curl_exec($this->curl_handle);
596 if ($getsize) {
597 $sizeinfo = curl_getinfo($this->curl_handle);
598 $existing_size = $sizeinfo['download_content_length'];
599 } else {
600 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) $IWP_MMB_Backup_Core->log("Curl: upload error: ".curl_error($this->curl_handle));
601 }
602 } else {
603 $existing_size = ftp_size($this->conn_id, $remote_file_path);
604 }
605 // In fact curl can return -1 as the value, for a non-existant file
606 if ($existing_size <=0) {
607 $resume = false;
608 $existing_size = 0;
609 } else {
610 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) $IWP_MMB_Backup_Core->log("File already exists at remote site: size $existing_size. Will attempt resumption.");
611 if ($existing_size >= $file_size) {
612 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) $IWP_MMB_Backup_Core->log("File is apparently already completely uploaded");
613 return true;
614 }
615 }
616 }
617
618 // From here on, $file_size is only used for logging calculations. We want to avoid divsion by zero.
619 $file_size = max($file_size, 1);
620
621 if (!$fh = fopen($local_file_path, 'rb')) return false;
622 if ($existing_size) fseek($fh, $existing_size);
623
624 // FTPS (i.e. implicit encryption)
625 if ($this->curl_handle) {
626 // Reset the curl object (because otherwise we get errors that make no sense)
627 $this->connect();
628 if (version_compare(phpversion(), '5.3.0', '>=')) {
629 curl_setopt($this->curl_handle, CURLOPT_PROGRESSFUNCTION, array($this, 'curl_progress_function'));
630 curl_setopt($this->curl_handle, CURLOPT_NOPROGRESS, false);
631 }
632 $this->upload_local_path = $local_file_path;
633 $this->upload_last_recorded_percent = 0;
634 $this->upload_size = max($file_size, 1);
635 $this->upload_from = $existing_size;
636 $this->uploaded_bytes = $existing_size;
637 curl_setopt($this->curl_handle, CURLOPT_URL, 'ftps://'.$this->host.'/'.$remote_file_path);
638 if ($existing_size) curl_setopt($this->curl_handle, CURLOPT_FTPAPPEND, true);
639
640 // DOn't set CURLOPT_UPLOAD=true before doing the size check - it results in a bizarre error
641 curl_setopt($this->curl_handle, CURLOPT_UPLOAD, true);
642 curl_setopt($this->curl_handle, CURLOPT_INFILE, $fh);
643 $output = curl_exec($this->curl_handle);
644 fclose($fh);
645 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core') && !$output) {
646 $iwp_backup_core->log("FTPS error: ".curl_error($this->curl_handle));
647 } elseif (true === $iwp_backup_core && !$output) {
648 echo __('Error:', 'InfiniteWP').' '.curl_error($this->curl_handle)."\n";
649 }
650 // Mark as used
651 $this->curl_handle = true;
652 return $output;
653 }
654
655 $ret = ftp_nb_fput($this->conn_id, $remote_file_path, $fh, FTP_BINARY, $existing_size);
656
657 // $existing_size can now be re-purposed
658
659 while (FTP_MOREDATA == $ret) {
660 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) {
661 $new_size = ftell($fh);
662 if ($new_size - $existing_size > 524288) {
663 $existing_size = $new_size;
664 $percent = round(100*$new_size/$file_size, 1);
665 $iwp_backup_core->record_uploaded_chunk($percent, '', $local_file_path);
666 }
667 }
668 // Continue upload
669 $ret = ftp_nb_continue($this->conn_id);
670 }
671
672 fclose($fh);
673
674 if (FTP_FINISHED != $ret) {
675 if (is_a($iwp_backup_core, 'IWP_MMB_Backup_Core')) $iwp_backup_core->log("FTP upload: error ($ret)");
676 return false;
677 }
678
679 return true;
680
681 }
682
683
684 public function get($local_file_path, $remote_file_path, $mode = FTP_BINARY, $resume = false, $iwp_backup_core = false) {
685
686 $file_last_size = 0;
687
688 if ($resume) {
689 if (!$fh = fopen($local_file_path, 'ab')) return false;
690 // @codingStandardsIgnoreLine
691 clearstatcache($local_file_path);
692 $file_last_size = filesize($local_file_path);
693 } else {
694 if (!$fh = fopen($local_file_path, 'wb')) return false;
695 }
696
697 // Implicit FTP, for which we use curl (since PHP's native FTP functions don't handle implicit FTP)
698 if ($this->curl_handle) {
699 if ($resume) curl_setopt($this->curl_handle, CURLOPT_RESUME_FROM, $resume);
700 curl_setopt($this->curl_handle, CURLOPT_NOBODY, false);
701 curl_setopt($this->curl_handle, CURLOPT_URL, 'ftps://'.$this->host.'/'.$remote_file_path);
702 curl_setopt($this->curl_handle, CURLOPT_UPLOAD, false);
703 curl_setopt($this->curl_handle, CURLOPT_FILE, $fh);
704 $output = curl_exec($this->curl_handle);
705 if ($output) {
706 if ($iwp_backup_core) $iwp_backup_core->log("FTP fetch: fetch complete");
707 } else {
708 if ($iwp_backup_core) $iwp_backup_core->log("FTP fetch: fetch failed");
709 }
710 return $output;
711 }
712
713 $ret = ftp_nb_fget($this->conn_id, $fh, $remote_file_path, $mode, $file_last_size);
714
715 if (false == $ret) return false;
716
717 while (FTP_MOREDATA == $ret) {
718
719 if($iwp_backup_core->restore_loop_break()){
720 return 'partial';
721 }
722
723 if ($iwp_backup_core) {
724 $file_now_size = filesize($local_file_path);
725 if ($file_now_size - $file_last_size > 524288) {
726 $iwp_backup_core->log("FTP fetch: file size is now: ".sprintf("%0.2f", filesize($local_file_path)/1048576)." MB");
727 $file_last_size = $file_now_size;
728 }
729 clearstatcache();
730 }
731
732 $ret = ftp_nb_continue($this->conn_id);
733 }
734
735 fclose($fh);
736
737 if (FTP_FINISHED == $ret) {
738 if ($iwp_backup_core) $iwp_backup_core->log("FTP fetch: fetch complete");
739 return true;
740 } else {
741 if ($iwp_backup_core) $iwp_backup_core->log("FTP fetch: fetch failed");
742 return false;
743 }
744
745 }
746
747 public function chmod($permissions, $remote_filename) {
748 if ($this->is_octal($permissions)) {
749 $result = ftp_chmod($this->conn_id, $permissions, $remote_filename);
750 return ($result) ? true : false;
751 } else {
752 throw new Exception('$permissions must be an octal number');
753 }
754 }
755
756 public function chdir($directory) {
757 ftp_chdir($this->conn_id, $directory);
758 }
759
760 public function delete($remote_file_path) {
761
762 if ($this->curl_handle) {
763 if (true === $this->curl_handle) $this->connect();
764 curl_setopt($this->curl_handle, CURLOPT_URL, 'ftps://'.$this->host.'/'.$remote_file_path);
765 curl_setopt($this->curl_handle, CURLOPT_RETURNTRANSFER, true);
766 curl_setopt($this->curl_handle, CURLOPT_QUOTE, array('DELE '.$remote_file_path));
767 // Unset some (possibly) previously-set options
768 curl_setopt($this->curl_handle, CURLOPT_UPLOAD, false);
769 curl_setopt($this->curl_handle, CURLOPT_INFILE, STDIN);
770 $output = curl_exec($this->curl_handle);
771 return $output;
772 }
773
774 return (ftp_delete($this->conn_id, $remote_file_path)) ? true : false;
775
776 }
777
778 public function make_dir($directory) {
779 if (ftp_mkdir($this->conn_id, $directory)) {
780 return true;
781 } else {
782 return false;
783 }
784 }
785
786 public function rename($old_name, $new_name) {
787 if (ftp_rename($this->conn_id, $old_name, $new_name)) {
788 return true;
789 } else {
790 return false;
791 }
792 }
793
794 public function remove_dir($directory) {
795 return ftp_rmdir($this->conn_id, $directory);
796 }
797
798 public function dir_list($directory) {
799 if ($this->curl_handle) {
800 // Can't get this to work - it might just be the vsftpd server I am testing on; it hangs strangely. But this means I can't test it.
801 return new WP_Error('unsupported_op', sprintf(__('The InfiniteWP module for this file access method (%s) does not support listing files', 'InfiniteWP'), 'FTP (SSL/Implicit)'));
802 if (true === $this->curl_handle) $this->connect();
803 curl_setopt($this->curl_handle, CURLOPT_URL, 'ftps://'.$this->host.'/'.trailingslashit($directory));
804 curl_setopt($this->curl_handle, CURLOPT_RETURNTRANSFER, true);
805 // curl_setopt($this->curl_handle, CURLOPT_FTPLISTONLY, true);
806 // curl_setopt($this->curl_handle, CURLOPT_POSTQUOTE, array('LIST'));
807 curl_setopt($this->curl_handle, CURLOPT_TIMEOUT, 10);
808 $output = curl_exec($this->curl_handle);
809 return $output;
810 }
811
812 return ftp_nlist($this->conn_id, $directory);
813 }
814
815 public function cdup() {
816 return ftp_cdup($this->conn_id);
817 }
818
819 public function size($f) {
820 return ($this->curl_handle) ? false : ftp_size($this->conn_id, $f);
821 }
822
823
824 public function current_dir() {
825 return ftp_pwd($this->conn_id);
826 }
827
828 private function is_octal($i) {
829 return decoct(octdec($i)) == $i;
830 }
831
832 public function __destruct() {
833 if ($this->conn_id) ftp_close($this->conn_id);
834 }
835 }
836 }
837