[horde] help with VFS?
Cliff Green
green at umdnj.edu
Fri Feb 3 10:34:30 PST 2006
Quoting Michael M Slusarz <slusarz at bigworm.curecanti.org>:
[munch]
> Without knowing what your SSH2-based VFS driver looks like (hint
> hint), we have no way of knowing what is wrong.
Yah. As soon as I hit "Send Message" I realized that... Please see
the attached.
Also, below is a patch for horde/test.php to test for the ssh2 module...
> Additionally, several VFS bugs have been fixed in Horde 3.1 so that
> may also be the issue.
I was concerned about being able to use it with the Release version of
Horde, wanting to avoid any unknown dependencies. If there aren't any,
I'll get to work on it. FWIW, this is based on the ftp driver. (or
did I say that already?)
<--- test.php patch begins --->
--- test.php.dist 2006-02-02 10:43:17.000000000 -0500
+++ test.php 2006-02-02 10:44:38.000000000 -0500
@@ -48,6 +48,7 @@
'ctype' => 'Ctype Support',
(version_compare(phpversion(), '5') < 0 ? 'domxml' : 'dom') =>
'DOM XML Support',
'ftp' => 'FTP Support',
+ 'ssh2' => 'SSH2 Support',
'gd' => 'GD Support',
'gettext' => array(
'descrip' => 'Gettext Support',
<----- test.php patch ends ----->
c
--
Cliff Green
BS&T
UMDNJ
-------------- next part --------------
<?php
/**
* VFS implementation for an SSH server.
*
* Required values for $params:<pre>
* 'username' The username with which to connect to the ssh2 server.
* 'password' The password with which to connect to the ssh2 server.
* 'hostspec' The ssh2 server to connect to.</pre>
*
* Optional values for $params:<pre>
* 'port' The port used to connect to the ssh2 server if other
* than 22.
*
* $Horde: framework/VFS/VFS/ssh2.php,v 1.75.4.9 2005/08/24 14:04:39 selsky Exp $
*
* Copyright 2002-2005 Chuck Hagenbuch <chuck at horde.org>
* Copyright 2002-2005 Michael Varghese <mike.varghese at ascellatech.com>
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @author Chuck Hagenbuch <chuck at horde.org>
* @author Michael Varghese <mike.varghese at ascellatech.com>
* @butcher Cliff Green <green at umdnj.edu>
* @since Horde 2.2
* @package VFS
*/
class VFS_ssh2 extends VFS {
/**
* List of additional credentials required for this VFS backend.
*
* @var array
*/
var $_credentials = array('username', 'password');
/**
* List of permissions and if they can be changed in this VFS backend.
*
* @var array
*/
var $_permissions = array(
'owner' => array('read' => true, 'write' => true, 'execute' => true),
'group' => array('read' => true, 'write' => true, 'execute' => true),
'all' => array('read' => true, 'write' => true, 'execute' => true));
/**
* Variable holding the connection to the ssh2 server.
*
* @var resource
*/
var $_stream = false;
/**
* Retrieves a file from the VFS.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return string The file data.
*/
function read($path, $name)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$tmpFile = $this->_getTempFile();
$fetch = @ssh2_scp_recv($this->_stream, $this->_getPath($path, $name), $tmpFile);
if ($fetch === false) {
return PEAR::raiseError(sprintf(_("Unable to open VFS file \"%s\"."), $this->_getPath($path, $name)));
}
$size = filesize($tmpFile);
if ($size === 0) {
return '';
}
if (OS_WINDOWS) {
$mode = 'rb';
} else {
$mode = 'r';
}
$fp = fopen($tmpFile, $mode);
$data = fread($fp, $size);
fclose($fp);
unlink($tmpFile);
return $data;
}
/**
* Stores a file in the VFS.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $tmpFile The temporary file containing the data to
* be stored.
* @param boolean $autocreate Automatically create directories?
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function write($path, $name, $tmpFile, $autocreate = false)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
if (!@ssh2_scp_send($this->_stream, $tmpFile, $this->_getPath($path, $name))) {
if ($autocreate) {
$result = $this->autocreatePath($path);
if (is_a($result, 'PEAR_Error')) {
return $result;
}
if (!@ssh2_scp_send($this->_stream, $tmpFile, $this->_getPath($path, $name))) {
return PEAR::raiseError(sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
}
} else {
return PEAR::raiseError(sprintf(_("Unable to write VFS file \"%s\"."), $this->_getPath($path, $name)));
}
}
return true;
}
/**
* Stores a file in the VFS from raw data.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $data The file data.
* @param boolean $autocreate Automatically create directories?
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function writeData($path, $name, $data, $autocreate = false)
{
$tmpFile = $this->_getTempFile();
$fp = fopen($tmpFile, 'wb');
fwrite($fp, $data);
fclose($fp);
$result = $this->write($path, $name, $tmpFile, $autocreate);
unlink($tmpFile);
return $result;
}
/**
* Deletes a file from the VFS.
*
* @param string $path The path to delete the file from.
* @param string $name The filename to delete.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function deleteFile($path, $name)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
if (!@ssh2_unlink($sftp, $this->_getPath($path, $name))) {
return PEAR::raiseError(sprintf(_("Unable to delete VFS file \"%s\"."), $this->_getPath($path, $name)));
}
return true;
}
/**
* Checks if a given item is a folder.
*
* @param string $path The parent folder.
* @param string $name The item name.
*
* @return boolean True if it is a folder, false otherwise.
*/
function isFolder($path, $name)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$result = false;
$olddir = $this->getCurrentDirectory();
/* See if we can stat the remote filename */
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
$statinfo = @ssh2_stat($sftp, $this->_getPath($path, $name));
if ($statinfo['mode'] & 040000) {
$result = true;
}
return $result;
}
/**
* Deletes a folder from the VFS.
*
* @param string $path The parent folder.
* @param string $name The name of the folder to delete.
* @param boolean $recursive Force a recursive delete?
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function deleteFolder($path, $name, $recursive = false)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
@ssh2_sftp_rmdir($sftp, $this->_getPath($path, $name));
return true;
}
/**
* Renames a file in the VFS.
*
* @param string $oldpath The old path to the file.
* @param string $oldname The old filename.
* @param string $newpath The new path of the file.
* @param string $newname The new filename.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function rename($oldpath, $oldname, $newpath, $newname)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
if (!@ssh2_sftp_rename($sftp, $this->_getPath($oldpath, $oldname), $this->_getPath($newpath, $newname))) {
return PEAR::raiseError(sprintf(_("Unable to rename VFS file \"%s\"."), $this->_getPath($oldpath, $oldname)));
}
return true;
}
/**
* Creates a folder on the VFS.
*
* @param string $path The parent folder.
* @param string $name The name of the new folder.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function createFolder($path, $name)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
if (!@ssh2_sftp_mkdir($sftp, $this->_getPath($path, $name))) {
return PEAR::raiseError(sprintf(_("Unable to create VFS directory \"%s\"."), $this->_getPath($path, $name)));
}
return true;
}
/**
* Changes permissions for an item on the VFS.
*
* @param string $path The parent folder of the item.
* @param string $name The name of the item.
* @param string $permission The permission to set.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function changePermissions($path, $name, $permission)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
if (!@ssh2_exec($conn, 'chmod ' . $permission . ' ' . $this->_getPath($path, $name))) {
return PEAR::raiseError(sprintf(_("Unable to change permission for VFS file \"%s\"."), $this->_getPath($path, $name)));
}
return true;
}
/**
* Returns an an unsorted file list of the specified directory.
*
* @param string $path The path of the directory.
* @param mixed $filter String/hash to filter file/dirname on.
* @param boolean $dotfiles Show dotfiles?
* @param boolean $dironly Show only directories?
*
* @return array File list on success or PEAR_Error on failure.
*/
function _listFolder($path = '', $filter = null, $dotfiles = true,
$dironly = false)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$files = array();
// Go with unix-style listings by default.
$type = 'unix';
$olddir = $this->getCurrentDirectory();
if (!empty($path)) {
$res = $this->_setPath($path);
if (is_a($res, 'PEAR_Error')) {
return $res;
}
}
if ($type == 'unix') {
// If we don't want dotfiles, We can save work here by not
// doing an ls -a and then not doing the check later (by
// setting $dotfiles to true, the if is short-circuited).
if ($dotfiles) {
$list = ssh2_exec($conn, 'ls -al');
$dotfiles = true;
} else {
$list = ssh2_exec($conn, 'ls -l');
}
} else {
$list = ssh2_exec($conn, '');
}
if (!is_array($list)) {
if (isset($olddir)) {
$res = $this->_setPath($olddir);
if (is_a($res, 'PEAR_Error')) {
return $res;
}
}
return array();
}
foreach ($list as $line) {
$file = array();
$item = preg_split('/\s+/', $line);
if ($type == 'unix' || ($type == 'win' && !preg_match('|\d\d-\d\d-\d\d|', $item[0]))) {
if (count($item) < 8 || substr($line, 0, 5) == 'total') {
continue;
}
$file['perms'] = $item[0];
$file['owner'] = $item[2];
$file['group'] = $item[3];
$file['name'] = substr($line, strpos($line, sprintf("%s %2s %5s", $item[5], $item[6], $item[7])) + 13);
// Filter out '.' and '..' entries.
if (preg_match('/^\.\.?\/?$/', $file['name'])) {
continue;
}
// Filter out dotfiles if they aren't wanted.
if (!$dotfiles && substr($file['name'], 0, 1) == '.') {
continue;
}
$p1 = substr($file['perms'], 0, 1);
if ($p1 === 'l') {
$file['link'] = substr($file['name'], strpos($file['name'], '->') + 3);
$file['name'] = substr($file['name'], 0, strpos($file['name'], '->') - 1);
$file['type'] = '**sym';
if ($this->isFolder('', $file['link'])) {
$file['linktype'] = '**dir';
} else {
$parts = explode('/', $file['link']);
$name = explode('.', array_pop($parts));
if (count($name) == 1 || ($name[0] === '' && count($name) == 2)) {
$file['linktype'] = '**none';
} else {
$file['linktype'] = VFS::strtolower(array_pop($name));
}
}
} elseif ($p1 === 'd') {
$file['type'] = '**dir';
} else {
$name = explode('.', $file['name']);
if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
$file['type'] = '**none';
} else {
$file['type'] = VFS::strtolower($name[count($name) - 1]);
}
}
if ($file['type'] == '**dir') {
$file['size'] = -1;
} else {
$file['size'] = $item[4];
}
if (strpos($item[7], ':') !== false) {
$file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . date('Y', time()));
if ($file['date'] > time()) {
$file['date'] = strtotime($item[7] . ':00' . $item[5] . ' ' . $item[6] . ' ' . (date('Y', time()) - 1));
}
} else {
$file['date'] = strtotime('00:00:00' . $item[5] . ' ' . $item[6] . ' ' . $item[7]);
}
} else {
/* Handle Windows SSH servers returning DOS-style file
* listings. */
$file['perms'] = '';
$file['owner'] = '';
$file['group'] = '';
$file['name'] = $item[3];
$index = 4;
while ($index < count($item)) {
$file['name'] .= ' ' . $item[$index];
$index++;
}
$file['date'] = strtotime($item[0] . ' ' . $item[1]);
if ($item[2] == '<DIR>') {
$file['type'] = '**dir';
$file['size'] = -1;
} else {
$file['size'] = $item[2];
$name = explode('.', $file['name']);
if (count($name) == 1 || (substr($file['name'], 0, 1) === '.' && count($name) == 2)) {
$file['type'] = '**none';
} else {
$file['type'] = VFS::strtolower($name[count($name) - 1]);
}
}
}
// Filtering.
if ($this->_filterMatch($filter, $file['name'])) {
unset($file);
continue;
}
if ($dironly && $file['type'] !== '**dir') {
unset($file);
continue;
}
$files[$file['name']] = $file;
unset($file);
}
if (isset($olddir)) {
$res = $this->_setPath($olddir);
if (is_a($res, 'PEAR_Error')) {
return $res;
}
}
return $files;
}
/**
* Returns a sorted list of folders in the specified directory.
*
* @param string $path The path of the directory to get the
* directory list for.
* @param mixed $filter Hash of items to filter based on folderlist.
* @param boolean $dotfolders Include dotfolders?
*
* @return mixed Folder list on success or a PEAR_Error object on failure.
*/
function listFolders($path = '', $filter = null, $dotfolders = true)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$folders = array();
$folder = array();
$folderList = $this->listFolder($path, null, $dotfolders, true);
if (is_a($folderList, 'PEAR_Error')) {
return $folderList;
}
$folder['val'] = $this->_parentDir($path);
$folder['abbrev'] = '..';
$folder['label'] = '..';
$folders[$folder['val']] = $folder;
foreach ($folderList as $files) {
$folder['val'] = $this->_getPath($path, $files['name']);
$folder['abbrev'] = $files['name'];
$folder['label'] = $folder['val'];
$folders[$folder['val']] = $folder;
}
ksort($folders);
return $folders;
}
/**
* Copies a file through the backend.
*
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The name of the destination directory.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function copy($path, $name, $dest)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$fileCheck = $this->listFolder($dest, null, true);
if (is_a($fileCheck, 'PEAR_Error')) {
return $fileCheck;
}
foreach ($fileCheck as $file) {
if ($file['name'] == $name) {
return PEAR::raiseError(sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
}
}
$isDir = false;
$dirCheck = $this->listFolder($path, null, false);
if (is_a($dirCheck, 'PEAR_Error')) {
return $dirCheck;
}
foreach ($dirCheck as $file) {
if ($file['name'] == $name && $file['type'] == '**dir') {
$isDir = true;
break;
}
}
if ($isDir) {
$result = $this->createFolder($dest, $name);
if (is_a($result, 'PEAR_Error')) {
return $result;
}
$file_list = $this->listFolder($this->_getPath($path, $name));
foreach ($file_list as $file) {
$result = $this->copy($this->_getPath($path, $name), $file['name'], $this->_getPath($dest, $name));
if (is_a($result, 'PEAR_Error')) {
return $result;
}
}
} else {
$tmpFile = $this->_getTempFile();
$fetch = @ssh2_scp_recv($conn, $this->_getPath($path, $name), $tmpFile);
if (!$fetch) {
unlink($tmpFile);
return PEAR::raiseError(sprintf(_("Failed to copy from \"%s\"."), $this->_getPath($path, $name)));
}
if (!@ssh2_scp_send($conn, $tmpFile, $this->_getPath($dest, $name))) {
unlink($tmpFile);
return PEAR::raiseError(sprintf(_("Failed to copy to \"%s\"."), $this->_getPath($dest, $name)));
}
unlink($tmpFile);
}
return true;
}
/**
* Moves a file through the backend.
*
* @param string $path The path of the original file.
* @param string $name The name of the original file.
* @param string $dest The destination file name.
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function move($path, $name, $dest)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
/* create sftp resource */
$sftp = @ssh2_sftp( $conn );
$fileCheck = $this->listFolder($dest, null, true);
foreach ($fileCheck as $file) {
if ($file['name'] == $name) {
return PEAR::raiseError(sprintf(_("%s already exists."), $this->_getPath($dest, $name)));
}
}
if (!@ssh2_sftp_rename($sftp, $this->_getPath($path, $name), $this->_getPath($dest, $name))) {
return PEAR::raiseError(sprintf(_("Failed to move to \"%s\"."), $this->_getPath($dest, $name)));
}
return true;
}
/**
* Returns the current working directory on the SSH server.
*
* @return string The current working directory.
*/
function getCurrentDirectory()
{
if (is_a($conn = $this->_connect(), 'PEAR_Error')) {
return $conn;
}
return ssh2_exec($conn, 'pwd');
}
/**
* Changes the current directory on the server.
*
* @access private
*
* @param string $path The path to change to.
*
* @return mixed True on success, or a PEAR_Error on failure.
*/
function _setPath($path)
{
if (!@ssh2_exec($conn, 'cd $path')) {
return PEAR::raiseError(sprintf(_("Unable to change to %s."), $path));
}
return true;
}
/**
* Returns the full path of an item.
*
* @access private
*
* @param string $path The directory of the item.
* @param string $name The name of the item.
*
* @return mixed Full path to the file when $path is not empty and just
* $name when not set.
*/
function _getPath($path, $name)
{
if ($path !== '') {
return ($path . '/' . $name);
}
return ($name);
}
/**
* Returns the parent directory of the specified path.
*
* @access private
*
* @param string $path The path to get the parent of.
*
* @return string The parent directory (string) on success or a PEAR_Error
* object on failure.
*/
function _parentDir($path)
{
$conn = $this->_connect();
if (is_a($conn, 'PEAR_Error')) {
return $conn;
}
$olddir = $this->getCurrentDirectory();
@ssh2_exec($conn, 'cd ..');
$parent = $this->getCurrentDirectory();
$this->_setPath($olddir);
if (!$parent) {
return PEAR::raiseError(_("Unable to determine current directory."));
}
return $parent;
}
/**
* Attempts to open a connection to the SSH server.
*
* @access private
*
* @return mixed True on success or a PEAR_Error object on failure.
*/
function _connect()
{
if ($this->_stream === false) {
if (!extension_loaded('ssh2')) {
return PEAR::raiseError(_("The SSH extension is not available."));
}
if (!is_array($this->_params)) {
return PEAR::raiseError(_("No configuration information specified for SSH VFS."));
}
$required = array('hostspec', 'username', 'password');
foreach ($required as $val) {
if (!isset($this->_params[$val])) {
return PEAR::raiseError(sprintf(_("Required \"%s\" not specified in VFS configuration."), $val));
}
}
/* Connect to the ssh2 server using the supplied parameters. */
$this->_stream = @ssh2_connect($this->_params['hostspec'], $this->_params['port']);
if (!$this->_stream) {
return PEAR::raiseError(_("Connection to SSH server failed."));
}
$connected = @ssh2_auth_password($this->_stream, $this->_params['username'], $this->_params['password']);
if (!$connected) {
$this->_disconnect();
return PEAR::raiseError(_("Authentication to SSH server failed."));
}
}
return true;
}
/**
* Disconnects from the SSH server and cleans up the connection.
*
* @access private
*/
function _disconnect()
{
@ssh2_exec($this->_stream, 'exit');
$this->_stream = false;
}
/**
* Determines the location of the system temporary directory. If a
* specific setting cannot be found, it defaults to /tmp
*
* @access private
*
* @return string A directory name which can be used for temp files.
* Returns false if one could not be found.
*/
function _getTempDir()
{
$tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', 'c:\windows\temp', 'c:\winnt\temp');
/* Try PHP's upload_tmp_dir directive. */
$tmp = ini_get('upload_tmp_dir');
/* Otherwise, try to determine the TMPDIR environment variable. */
if (empty($tmp)) {
$tmp = getenv('TMPDIR');
}
/* If we still cannot determine a value, then cycle through a list of
* preset possibilities. */
while (empty($tmp) && sizeof($tmp_locations)) {
$tmp_check = array_shift($tmp_locations);
if (@is_dir($tmp_check)) {
$tmp = $tmp_check;
}
}
/* If it is still empty, we have failed, so return false; otherwise
* return the directory determined. */
return empty($tmp) ? false : $tmp;
}
/**
* Creates a temporary file.
*
* @access private
*
* @return string Returns the full path-name to the temporary file.
* Returns false if a temp file could not be created.
*/
function _getTempFile()
{
$tmp_dir = $this->_getTempDir();
if (empty($tmp_dir)) {
return false;
}
$tmp_file = tempnam($tmp_dir, 'vfs');
/* If the file was created, then register it for deleton and return */
if (empty($tmp_file)) {
return false;
} else {
return $tmp_file;
}
}
}
More information about the horde
mailing list