[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress Filesystem Class for implementing SSH2 4 * 5 * To use this class you must follow these steps for PHP 5.2.6+ 6 * 7 * @contrib http://kevin.vanzonneveld.net/techblog/article/make_ssh_connections_with_php/ - Installation Notes 8 * 9 * Compile libssh2 (Note: Only 0.14 is officaly working with PHP 5.2.6+ right now, But many users have found the latest versions work) 10 * 11 * cd /usr/src 12 * wget https://www.libssh2.org/download/libssh2-0.14.tar.gz 13 * tar -zxvf libssh2-0.14.tar.gz 14 * cd libssh2-0.14/ 15 * ./configure 16 * make all install 17 * 18 * Note: Do not leave the directory yet! 19 * 20 * Enter: pecl install -f ssh2 21 * 22 * Copy the ssh.so file it creates to your PHP Module Directory. 23 * Open up your PHP.INI file and look for where extensions are placed. 24 * Add in your PHP.ini file: extension=ssh2.so 25 * 26 * Restart Apache! 27 * Check phpinfo() streams to confirm that: ssh2.shell, ssh2.exec, ssh2.tunnel, ssh2.scp, ssh2.sftp exist. 28 * 29 * Note: As of WordPress 2.8, this utilizes the PHP5+ function `stream_get_contents()`. 30 * 31 * @since 2.7.0 32 * 33 * @package WordPress 34 * @subpackage Filesystem 35 */ 36 class WP_Filesystem_SSH2 extends WP_Filesystem_Base { 37 38 /** 39 * @since 2.7.0 40 * @var resource 41 */ 42 public $link = false; 43 44 /** 45 * @since 2.7.0 46 * @var resource 47 */ 48 public $sftp_link; 49 50 /** 51 * @since 2.7.0 52 * @var bool 53 */ 54 public $keys = false; 55 56 /** 57 * Constructor. 58 * 59 * @since 2.7.0 60 * 61 * @param array $opt 62 */ 63 public function __construct( $opt = '' ) { 64 $this->method = 'ssh2'; 65 $this->errors = new WP_Error(); 66 67 // Check if possible to use ssh2 functions. 68 if ( ! extension_loaded( 'ssh2' ) ) { 69 $this->errors->add( 'no_ssh2_ext', __( 'The ssh2 PHP extension is not available' ) ); 70 return; 71 } 72 73 // Set defaults: 74 if ( empty( $opt['port'] ) ) { 75 $this->options['port'] = 22; 76 } else { 77 $this->options['port'] = $opt['port']; 78 } 79 80 if ( empty( $opt['hostname'] ) ) { 81 $this->errors->add( 'empty_hostname', __( 'SSH2 hostname is required' ) ); 82 } else { 83 $this->options['hostname'] = $opt['hostname']; 84 } 85 86 // Check if the options provided are OK. 87 if ( ! empty( $opt['public_key'] ) && ! empty( $opt['private_key'] ) ) { 88 $this->options['public_key'] = $opt['public_key']; 89 $this->options['private_key'] = $opt['private_key']; 90 91 $this->options['hostkey'] = array( 'hostkey' => 'ssh-rsa,ssh-ed25519' ); 92 93 $this->keys = true; 94 } elseif ( empty( $opt['username'] ) ) { 95 $this->errors->add( 'empty_username', __( 'SSH2 username is required' ) ); 96 } 97 98 if ( ! empty( $opt['username'] ) ) { 99 $this->options['username'] = $opt['username']; 100 } 101 102 if ( empty( $opt['password'] ) ) { 103 // Password can be blank if we are using keys. 104 if ( ! $this->keys ) { 105 $this->errors->add( 'empty_password', __( 'SSH2 password is required' ) ); 106 } 107 } else { 108 $this->options['password'] = $opt['password']; 109 } 110 } 111 112 /** 113 * Connects filesystem. 114 * 115 * @since 2.7.0 116 * 117 * @return bool True on success, false on failure. 118 */ 119 public function connect() { 120 if ( ! $this->keys ) { 121 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'] ); 122 } else { 123 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'], $this->options['hostkey'] ); 124 } 125 126 if ( ! $this->link ) { 127 $this->errors->add( 128 'connect', 129 sprintf( 130 /* translators: %s: hostname:port */ 131 __( 'Failed to connect to SSH2 Server %s' ), 132 $this->options['hostname'] . ':' . $this->options['port'] 133 ) 134 ); 135 136 return false; 137 } 138 139 if ( ! $this->keys ) { 140 if ( ! @ssh2_auth_password( $this->link, $this->options['username'], $this->options['password'] ) ) { 141 $this->errors->add( 142 'auth', 143 sprintf( 144 /* translators: %s: Username. */ 145 __( 'Username/Password incorrect for %s' ), 146 $this->options['username'] 147 ) 148 ); 149 150 return false; 151 } 152 } else { 153 if ( ! @ssh2_auth_pubkey_file( $this->link, $this->options['username'], $this->options['public_key'], $this->options['private_key'], $this->options['password'] ) ) { 154 $this->errors->add( 155 'auth', 156 sprintf( 157 /* translators: %s: Username. */ 158 __( 'Public and Private keys incorrect for %s' ), 159 $this->options['username'] 160 ) 161 ); 162 163 return false; 164 } 165 } 166 167 $this->sftp_link = ssh2_sftp( $this->link ); 168 169 if ( ! $this->sftp_link ) { 170 $this->errors->add( 171 'connect', 172 sprintf( 173 /* translators: %s: hostname:port */ 174 __( 'Failed to initialize a SFTP subsystem session with the SSH2 Server %s' ), 175 $this->options['hostname'] . ':' . $this->options['port'] 176 ) 177 ); 178 179 return false; 180 } 181 182 return true; 183 } 184 185 /** 186 * Gets the ssh2.sftp PHP stream wrapper path to open for the given file. 187 * 188 * This method also works around a PHP bug where the root directory (/) cannot 189 * be opened by PHP functions, causing a false failure. In order to work around 190 * this, the path is converted to /./ which is semantically the same as / 191 * See https://bugs.php.net/bug.php?id=64169 for more details. 192 * 193 * @since 4.4.0 194 * 195 * @param string $path The File/Directory path on the remote server to return 196 * @return string The ssh2.sftp:// wrapped path to use. 197 */ 198 public function sftp_path( $path ) { 199 if ( '/' === $path ) { 200 $path = '/./'; 201 } 202 203 return 'ssh2.sftp://' . $this->sftp_link . '/' . ltrim( $path, '/' ); 204 } 205 206 /** 207 * @since 2.7.0 208 * 209 * @param string $command 210 * @param bool $returnbool 211 * @return bool|string True on success, false on failure. String if the command was executed, `$returnbool` 212 * is false (default), and data from the resulting stream was retrieved. 213 */ 214 public function run_command( $command, $returnbool = false ) { 215 if ( ! $this->link ) { 216 return false; 217 } 218 219 $stream = ssh2_exec( $this->link, $command ); 220 221 if ( ! $stream ) { 222 $this->errors->add( 223 'command', 224 sprintf( 225 /* translators: %s: Command. */ 226 __( 'Unable to perform command: %s' ), 227 $command 228 ) 229 ); 230 } else { 231 stream_set_blocking( $stream, true ); 232 stream_set_timeout( $stream, FS_TIMEOUT ); 233 $data = stream_get_contents( $stream ); 234 fclose( $stream ); 235 236 if ( $returnbool ) { 237 return ( false === $data ) ? false : '' !== trim( $data ); 238 } else { 239 return $data; 240 } 241 } 242 243 return false; 244 } 245 246 /** 247 * Reads entire file into a string. 248 * 249 * @since 2.7.0 250 * 251 * @param string $file Name of the file to read. 252 * @return string|false Read data on success, false if no temporary file could be opened, 253 * or if the file couldn't be retrieved. 254 */ 255 public function get_contents( $file ) { 256 return file_get_contents( $this->sftp_path( $file ) ); 257 } 258 259 /** 260 * Reads entire file into an array. 261 * 262 * @since 2.7.0 263 * 264 * @param string $file Path to the file. 265 * @return array|false File contents in an array on success, false on failure. 266 */ 267 public function get_contents_array( $file ) { 268 return file( $this->sftp_path( $file ) ); 269 } 270 271 /** 272 * Writes a string to a file. 273 * 274 * @since 2.7.0 275 * 276 * @param string $file Remote path to the file where to write the data. 277 * @param string $contents The data to write. 278 * @param int|false $mode Optional. The file permissions as octal number, usually 0644. 279 * Default false. 280 * @return bool True on success, false on failure. 281 */ 282 public function put_contents( $file, $contents, $mode = false ) { 283 $ret = file_put_contents( $this->sftp_path( $file ), $contents ); 284 285 if ( strlen( $contents ) !== $ret ) { 286 return false; 287 } 288 289 $this->chmod( $file, $mode ); 290 291 return true; 292 } 293 294 /** 295 * Gets the current working directory. 296 * 297 * @since 2.7.0 298 * 299 * @return string|false The current working directory on success, false on failure. 300 */ 301 public function cwd() { 302 $cwd = ssh2_sftp_realpath( $this->sftp_link, '.' ); 303 304 if ( $cwd ) { 305 $cwd = trailingslashit( trim( $cwd ) ); 306 } 307 308 return $cwd; 309 } 310 311 /** 312 * Changes current directory. 313 * 314 * @since 2.7.0 315 * 316 * @param string $dir The new current directory. 317 * @return bool True on success, false on failure. 318 */ 319 public function chdir( $dir ) { 320 return $this->run_command( 'cd ' . $dir, true ); 321 } 322 323 /** 324 * Changes the file group. 325 * 326 * @since 2.7.0 327 * 328 * @param string $file Path to the file. 329 * @param string|int $group A group name or number. 330 * @param bool $recursive Optional. If set to true, changes file group recursively. 331 * Default false. 332 * @return bool True on success, false on failure. 333 */ 334 public function chgrp( $file, $group, $recursive = false ) { 335 if ( ! $this->exists( $file ) ) { 336 return false; 337 } 338 339 if ( ! $recursive || ! $this->is_dir( $file ) ) { 340 return $this->run_command( sprintf( 'chgrp %s %s', escapeshellarg( $group ), escapeshellarg( $file ) ), true ); 341 } 342 343 return $this->run_command( sprintf( 'chgrp -R %s %s', escapeshellarg( $group ), escapeshellarg( $file ) ), true ); 344 } 345 346 /** 347 * Changes filesystem permissions. 348 * 349 * @since 2.7.0 350 * 351 * @param string $file Path to the file. 352 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 353 * 0755 for directories. Default false. 354 * @param bool $recursive Optional. If set to true, changes file permissions recursively. 355 * Default false. 356 * @return bool True on success, false on failure. 357 */ 358 public function chmod( $file, $mode = false, $recursive = false ) { 359 if ( ! $this->exists( $file ) ) { 360 return false; 361 } 362 363 if ( ! $mode ) { 364 if ( $this->is_file( $file ) ) { 365 $mode = FS_CHMOD_FILE; 366 } elseif ( $this->is_dir( $file ) ) { 367 $mode = FS_CHMOD_DIR; 368 } else { 369 return false; 370 } 371 } 372 373 if ( ! $recursive || ! $this->is_dir( $file ) ) { 374 return $this->run_command( sprintf( 'chmod %o %s', $mode, escapeshellarg( $file ) ), true ); 375 } 376 377 return $this->run_command( sprintf( 'chmod -R %o %s', $mode, escapeshellarg( $file ) ), true ); 378 } 379 380 /** 381 * Changes the owner of a file or directory. 382 * 383 * @since 2.7.0 384 * 385 * @param string $file Path to the file or directory. 386 * @param string|int $owner A user name or number. 387 * @param bool $recursive Optional. If set to true, changes file owner recursively. 388 * Default false. 389 * @return bool True on success, false on failure. 390 */ 391 public function chown( $file, $owner, $recursive = false ) { 392 if ( ! $this->exists( $file ) ) { 393 return false; 394 } 395 396 if ( ! $recursive || ! $this->is_dir( $file ) ) { 397 return $this->run_command( sprintf( 'chown %s %s', escapeshellarg( $owner ), escapeshellarg( $file ) ), true ); 398 } 399 400 return $this->run_command( sprintf( 'chown -R %s %s', escapeshellarg( $owner ), escapeshellarg( $file ) ), true ); 401 } 402 403 /** 404 * Gets the file owner. 405 * 406 * @since 2.7.0 407 * 408 * @param string $file Path to the file. 409 * @return string|false Username of the owner on success, false on failure. 410 */ 411 public function owner( $file ) { 412 $owneruid = @fileowner( $this->sftp_path( $file ) ); 413 414 if ( ! $owneruid ) { 415 return false; 416 } 417 418 if ( ! function_exists( 'posix_getpwuid' ) ) { 419 return $owneruid; 420 } 421 422 $ownerarray = posix_getpwuid( $owneruid ); 423 424 if ( ! $ownerarray ) { 425 return false; 426 } 427 428 return $ownerarray['name']; 429 } 430 431 /** 432 * Gets the permissions of the specified file or filepath in their octal format. 433 * 434 * @since 2.7.0 435 * 436 * @param string $file Path to the file. 437 * @return string Mode of the file (the last 3 digits). 438 */ 439 public function getchmod( $file ) { 440 return substr( decoct( @fileperms( $this->sftp_path( $file ) ) ), -3 ); 441 } 442 443 /** 444 * Gets the file's group. 445 * 446 * @since 2.7.0 447 * 448 * @param string $file Path to the file. 449 * @return string|false The group on success, false on failure. 450 */ 451 public function group( $file ) { 452 $gid = @filegroup( $this->sftp_path( $file ) ); 453 454 if ( ! $gid ) { 455 return false; 456 } 457 458 if ( ! function_exists( 'posix_getgrgid' ) ) { 459 return $gid; 460 } 461 462 $grouparray = posix_getgrgid( $gid ); 463 464 if ( ! $grouparray ) { 465 return false; 466 } 467 468 return $grouparray['name']; 469 } 470 471 /** 472 * Copies a file. 473 * 474 * @since 2.7.0 475 * 476 * @param string $source Path to the source file. 477 * @param string $destination Path to the destination file. 478 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists. 479 * Default false. 480 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files, 481 * 0755 for dirs. Default false. 482 * @return bool True on success, false on failure. 483 */ 484 public function copy( $source, $destination, $overwrite = false, $mode = false ) { 485 if ( ! $overwrite && $this->exists( $destination ) ) { 486 return false; 487 } 488 489 $content = $this->get_contents( $source ); 490 491 if ( false === $content ) { 492 return false; 493 } 494 495 return $this->put_contents( $destination, $content, $mode ); 496 } 497 498 /** 499 * Moves a file. 500 * 501 * @since 2.7.0 502 * 503 * @param string $source Path to the source file. 504 * @param string $destination Path to the destination file. 505 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists. 506 * Default false. 507 * @return bool True on success, false on failure. 508 */ 509 public function move( $source, $destination, $overwrite = false ) { 510 if ( $this->exists( $destination ) ) { 511 if ( $overwrite ) { 512 // We need to remove the destination file before we can rename the source. 513 $this->delete( $destination, false, 'f' ); 514 } else { 515 // If we're not overwriting, the rename will fail, so return early. 516 return false; 517 } 518 } 519 520 return ssh2_sftp_rename( $this->sftp_link, $source, $destination ); 521 } 522 523 /** 524 * Deletes a file or directory. 525 * 526 * @since 2.7.0 527 * 528 * @param string $file Path to the file or directory. 529 * @param bool $recursive Optional. If set to true, deletes files and folders recursively. 530 * Default false. 531 * @param string|false $type Type of resource. 'f' for file, 'd' for directory. 532 * Default false. 533 * @return bool True on success, false on failure. 534 */ 535 public function delete( $file, $recursive = false, $type = false ) { 536 if ( 'f' === $type || $this->is_file( $file ) ) { 537 return ssh2_sftp_unlink( $this->sftp_link, $file ); 538 } 539 540 if ( ! $recursive ) { 541 return ssh2_sftp_rmdir( $this->sftp_link, $file ); 542 } 543 544 $filelist = $this->dirlist( $file ); 545 546 if ( is_array( $filelist ) ) { 547 foreach ( $filelist as $filename => $fileinfo ) { 548 $this->delete( $file . '/' . $filename, $recursive, $fileinfo['type'] ); 549 } 550 } 551 552 return ssh2_sftp_rmdir( $this->sftp_link, $file ); 553 } 554 555 /** 556 * Checks if a file or directory exists. 557 * 558 * @since 2.7.0 559 * 560 * @param string $file Path to file or directory. 561 * @return bool Whether $file exists or not. 562 */ 563 public function exists( $file ) { 564 return file_exists( $this->sftp_path( $file ) ); 565 } 566 567 /** 568 * Checks if resource is a file. 569 * 570 * @since 2.7.0 571 * 572 * @param string $file File path. 573 * @return bool Whether $file is a file. 574 */ 575 public function is_file( $file ) { 576 return is_file( $this->sftp_path( $file ) ); 577 } 578 579 /** 580 * Checks if resource is a directory. 581 * 582 * @since 2.7.0 583 * 584 * @param string $path Directory path. 585 * @return bool Whether $path is a directory. 586 */ 587 public function is_dir( $path ) { 588 return is_dir( $this->sftp_path( $path ) ); 589 } 590 591 /** 592 * Checks if a file is readable. 593 * 594 * @since 2.7.0 595 * 596 * @param string $file Path to file. 597 * @return bool Whether $file is readable. 598 */ 599 public function is_readable( $file ) { 600 return is_readable( $this->sftp_path( $file ) ); 601 } 602 603 /** 604 * Checks if a file or directory is writable. 605 * 606 * @since 2.7.0 607 * 608 * @param string $file Path to file or directory. 609 * @return bool Whether $file is writable. 610 */ 611 public function is_writable( $file ) { 612 // PHP will base its writable checks on system_user === file_owner, not ssh_user === file_owner. 613 return true; 614 } 615 616 /** 617 * Gets the file's last access time. 618 * 619 * @since 2.7.0 620 * 621 * @param string $file Path to file. 622 * @return int|false Unix timestamp representing last access time, false on failure. 623 */ 624 public function atime( $file ) { 625 return fileatime( $this->sftp_path( $file ) ); 626 } 627 628 /** 629 * Gets the file modification time. 630 * 631 * @since 2.7.0 632 * 633 * @param string $file Path to file. 634 * @return int|false Unix timestamp representing modification time, false on failure. 635 */ 636 public function mtime( $file ) { 637 return filemtime( $this->sftp_path( $file ) ); 638 } 639 640 /** 641 * Gets the file size (in bytes). 642 * 643 * @since 2.7.0 644 * 645 * @param string $file Path to file. 646 * @return int|false Size of the file in bytes on success, false on failure. 647 */ 648 public function size( $file ) { 649 return filesize( $this->sftp_path( $file ) ); 650 } 651 652 /** 653 * Sets the access and modification times of a file. 654 * 655 * Note: Not implemented. 656 * 657 * @since 2.7.0 658 * 659 * @param string $file Path to file. 660 * @param int $time Optional. Modified time to set for file. 661 * Default 0. 662 * @param int $atime Optional. Access time to set for file. 663 * Default 0. 664 */ 665 public function touch( $file, $time = 0, $atime = 0 ) { 666 // Not implemented. 667 } 668 669 /** 670 * Creates a directory. 671 * 672 * @since 2.7.0 673 * 674 * @param string $path Path for new directory. 675 * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod). 676 * Default false. 677 * @param string|int|false $chown Optional. A user name or number (or false to skip chown). 678 * Default false. 679 * @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp). 680 * Default false. 681 * @return bool True on success, false on failure. 682 */ 683 public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) { 684 $path = untrailingslashit( $path ); 685 686 if ( empty( $path ) ) { 687 return false; 688 } 689 690 if ( ! $chmod ) { 691 $chmod = FS_CHMOD_DIR; 692 } 693 694 if ( ! ssh2_sftp_mkdir( $this->sftp_link, $path, $chmod, true ) ) { 695 return false; 696 } 697 698 // Set directory permissions. 699 ssh2_sftp_chmod( $this->sftp_link, $path, $chmod ); 700 701 if ( $chown ) { 702 $this->chown( $path, $chown ); 703 } 704 705 if ( $chgrp ) { 706 $this->chgrp( $path, $chgrp ); 707 } 708 709 return true; 710 } 711 712 /** 713 * Deletes a directory. 714 * 715 * @since 2.7.0 716 * 717 * @param string $path Path to directory. 718 * @param bool $recursive Optional. Whether to recursively remove files/directories. 719 * Default false. 720 * @return bool True on success, false on failure. 721 */ 722 public function rmdir( $path, $recursive = false ) { 723 return $this->delete( $path, $recursive ); 724 } 725 726 /** 727 * Gets details for files in a directory or a specific file. 728 * 729 * @since 2.7.0 730 * 731 * @param string $path Path to directory or file. 732 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files. 733 * Default true. 734 * @param bool $recursive Optional. Whether to recursively include file details in nested directories. 735 * Default false. 736 * @return array|false { 737 * Array of files. False if unable to list directory contents. 738 * 739 * @type string $name Name of the file or directory. 740 * @type string $perms *nix representation of permissions. 741 * @type string $permsn Octal representation of permissions. 742 * @type string $owner Owner name or ID. 743 * @type int $size Size of file in bytes. 744 * @type int $lastmodunix Last modified unix timestamp. 745 * @type mixed $lastmod Last modified month (3 letter) and day (without leading 0). 746 * @type int $time Last modified time. 747 * @type string $type Type of resource. 'f' for file, 'd' for directory. 748 * @type mixed $files If a directory and `$recursive` is true, contains another array of files. 749 * } 750 */ 751 public function dirlist( $path, $include_hidden = true, $recursive = false ) { 752 if ( $this->is_file( $path ) ) { 753 $limit_file = basename( $path ); 754 $path = dirname( $path ); 755 } else { 756 $limit_file = false; 757 } 758 759 if ( ! $this->is_dir( $path ) || ! $this->is_readable( $path ) ) { 760 return false; 761 } 762 763 $ret = array(); 764 $dir = dir( $this->sftp_path( $path ) ); 765 766 if ( ! $dir ) { 767 return false; 768 } 769 770 while ( false !== ( $entry = $dir->read() ) ) { 771 $struc = array(); 772 $struc['name'] = $entry; 773 774 if ( '.' === $struc['name'] || '..' === $struc['name'] ) { 775 continue; // Do not care about these folders. 776 } 777 778 if ( ! $include_hidden && '.' === $struc['name'][0] ) { 779 continue; 780 } 781 782 if ( $limit_file && $struc['name'] !== $limit_file ) { 783 continue; 784 } 785 786 $struc['perms'] = $this->gethchmod( $path . '/' . $entry ); 787 $struc['permsn'] = $this->getnumchmodfromh( $struc['perms'] ); 788 $struc['number'] = false; 789 $struc['owner'] = $this->owner( $path . '/' . $entry ); 790 $struc['group'] = $this->group( $path . '/' . $entry ); 791 $struc['size'] = $this->size( $path . '/' . $entry ); 792 $struc['lastmodunix'] = $this->mtime( $path . '/' . $entry ); 793 $struc['lastmod'] = gmdate( 'M j', $struc['lastmodunix'] ); 794 $struc['time'] = gmdate( 'h:i:s', $struc['lastmodunix'] ); 795 $struc['type'] = $this->is_dir( $path . '/' . $entry ) ? 'd' : 'f'; 796 797 if ( 'd' === $struc['type'] ) { 798 if ( $recursive ) { 799 $struc['files'] = $this->dirlist( $path . '/' . $struc['name'], $include_hidden, $recursive ); 800 } else { 801 $struc['files'] = array(); 802 } 803 } 804 805 $ret[ $struc['name'] ] = $struc; 806 } 807 808 $dir->close(); 809 unset( $dir ); 810 811 return $ret; 812 } 813 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |