VFS
[ class tree: VFS ] [ index: VFS ] [ all elements ]

Source for file smb.php

Documentation is available at smb.php

  1. <?php
  2. /**
  3.  * Stateless VFS implementation for a SMB server, based on smbclient.
  4.  *
  5.  * Required values for $params:
  6.  * <pre>
  7.  *   'username'  - The username with which to connect to the SMB server.
  8.  *   'password'  - The password with which to connect to the SMB server.
  9.  *   'hostspec'  - The SMB server to connect to.
  10.  *   'port'      - The SMB port number to connect to.
  11.  *   'share'     - The share to access on the SMB server.
  12.  *   'smbclient' - The path to the 'smbclient' executable.
  13.  * </pre>
  14.  *
  15.  * Optional values for $params:
  16.  * <pre>
  17.  *   'ipaddress' - The address of the server to connect to.
  18.  * </pre>
  19.  *
  20.  * Functions not implemented:
  21.  *   - changePermissions(): The SMB permission style does not fit with the
  22.  *                          module.
  23.  *
  24.  * $Horde: framework/VFS/VFS/smb.php,v 1.17 2006/03/30 08:03:09 selsky Exp $
  25.  *
  26.  * Codebase copyright 2002 Paul Gareau <paul@xhawk.net>.  Adapted with
  27.  * permission by Patrice Levesque <wayne@ptaff.ca> from phpsmb-0.8 code, and
  28.  * converted to the LGPL.  Please do not taunt original author, contact
  29.  * Patrice Levesque or dev@lists.horde.org.
  30.  *
  31.  * See the enclosed file COPYING for license information (LGPL). If you
  32.  * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
  33.  *
  34.  * @author  Paul Gareau <paul@xhawk.net>
  35.  * @author  Patrice Levesque <wayne@ptaff.ca>
  36.  * @since   Horde 3.1
  37.  * @package VFS
  38.  */
  39. class VFS_smb extends VFS {
  40.  
  41.     /**
  42.      * List of additional credentials required for this VFS backend.
  43.      *
  44.      * @var array 
  45.      */
  46.     var $_credentials = array('username''password');
  47.  
  48.     /**
  49.      * List of permissions and if they can be changed in this VFS backend.
  50.      *
  51.      * @var array 
  52.      */
  53.     var $_permissions = array(
  54.         'owner' => array('read' => false'write' => false'execute' => false),
  55.         'group' => array('read' => false'write' => false'execute' => false),
  56.         'all'   => array('read' => false'write' => false'execute' => false));
  57.  
  58.     /**
  59.      * Authenticates a user on the SMB server and share.
  60.      *
  61.      * @access private
  62.      *
  63.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  64.      */
  65.     function _connect()
  66.     {
  67.         $cmd = array('quit');
  68.         $err $this->_command(''$cmd);
  69.         if (is_a($err'PEAR_Error')) {
  70.             return PEAR::raiseError(_("Authentication to the SMB server failed."));
  71.         }
  72.         return true;
  73.     }
  74.  
  75.     /**
  76.      * Retrieves a file from the VFS.
  77.      *
  78.      * @param string $path  The pathname to the file.
  79.      * @param string $name  The filename to retrieve.
  80.      *
  81.      * @return string  The file data.
  82.      */
  83.     function read($path$name)
  84.     {
  85.         list($path$name$this->_escapeShellCommand($path$name);
  86.         $temp $this->_getTempFile();
  87.         $cmd = array('get \"' $name '\" ' $temp);
  88.         $err $this->_command($path$cmd);
  89.         if (is_a($err'PEAR_Error')) {
  90.             return $err;
  91.         }
  92.         if (!file_exists($temp)) {
  93.             return PEAR::raiseError(sprintf(_("Unable to open VFS file \"%s\".")$this->_getPath($path$name)));
  94.         }
  95.         if (function_exists('file_get_contents')) {
  96.             $file file_get_contents($temp);
  97.         else {
  98.             $fp fopen($temp'r');
  99.             $file fread($fpfilesize($temp));
  100.             fclose($fp);
  101.         }
  102.         unlink($temp);
  103.         if ($file{
  104.             return $file;
  105.         else {
  106.             return PEAR::raiseError(sprintf(_("Unable to open VFS file \"%s\".")$this->_getPath($path$name)));
  107.         }
  108.     }
  109.  
  110.     /**
  111.      * Stores a file in the VFS.
  112.      *
  113.      * @param string $path         The path to store the file in.
  114.      * @param string $name         The filename to use.
  115.      * @param string $tmpFile      The temporary file containing the data to be
  116.      *                              stored.
  117.      * @param boolean $autocreate  Automatically create directories?
  118.      *
  119.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  120.      */
  121.     function write($path$name$tmpFile$autocreate = false)
  122.     {
  123.         list($path$name$this->_escapeShellCommand($path$name);
  124.         $cmd = array('put \"' $tmpFile '\" \"' $name '\"');
  125.         // do we need to first autocreate the directory?
  126.         if ($autocreate{
  127.             $result $this->autocreatePath($path);
  128.             if (is_a($result'PEAR_Error')) {
  129.                 return $result;
  130.             }
  131.         }
  132.         $err $this->_command($path$cmd);
  133.         if (is_a($err'PEAR_Error')) {
  134.             return $err;
  135.         }
  136.         return true;
  137.     }
  138.  
  139.     /**
  140.      * Stores a file in the VFS from raw data.
  141.      *
  142.      * @param string $path         The path to store the file in.
  143.      * @param string $name         The filename to use.
  144.      * @param string $data         The file data.
  145.      * @param boolean $autocreate  Automatically create directories?
  146.      *
  147.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  148.      */
  149.     function writeData($path$name$data$autocreate = false)
  150.     {
  151.         $tmpFile $this->_getTempFile();
  152.         $fp fopen($tmpFile'wb');
  153.         fwrite($fp$data);
  154.         fclose($fp);
  155.         $result $this->write($path$name$tmpFile$autocreate);
  156.         unlink($tmpFile);
  157.         return $result;
  158.     }
  159.  
  160.     /**
  161.      * Deletes a file from the VFS.
  162.      *
  163.      * @param string $path  The path to delete the file from.
  164.      * @param string $name  The filename to use.
  165.      *
  166.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  167.      */
  168.     function deleteFile($path$name)
  169.     {
  170.         list($path$name$this->_escapeShellCommand($path$name);
  171.         $cmd = array('del \"' $name '\"');
  172.         $err $this->_command($path$cmd);
  173.         if (is_a($err'PEAR_Error')) {
  174.             return $err;
  175.         }
  176.         return true;
  177.     }
  178.  
  179.     /**
  180.      * Checks if a given pathname is a folder.
  181.      *
  182.      * @param string $path  The path to the folder.
  183.      * @param string $name  The file or folder name.
  184.      *
  185.      * @return boolean  True if it is a folder, false otherwise.
  186.      */
  187.     function isFolder($path$name)
  188.     {
  189.         list($path$name$this->_escapeShellCommand($path$name);
  190.         $cmd = array('quit');
  191.         $err $this->_command($this->_getPath($path$name)$cmd);
  192.         if (is_a($err'PEAR_Error')) {
  193.             return false;
  194.         }
  195.         return true;
  196.     }
  197.  
  198.     /**
  199.      * Deletes a folder from the VFS.
  200.      *
  201.      * @param string $path        The path to delete the folder from.
  202.      * @param string $name        The name of the folder to delete.
  203.      * @param boolean $recursive  Force a recursive delete?
  204.      *
  205.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  206.      */
  207.     function deleteFolder($path$name$recursive = false)
  208.     {
  209.         if (!$this->isFolder($path$name)) {
  210.             return PEAR::raiseError(sprintf(_("\"%s\" is not a directory.")$path '/' $name));
  211.         }
  212.  
  213.         $file_list $this->listFolder($this->_getPath($path$name));
  214.         if (is_a($file_list'PEAR_Error')) {
  215.             return $file_list;
  216.         }
  217.  
  218.         if ($file_list && !$recursive{
  219.             return PEAR::raiseError(sprintf(_("Unable to delete \"%s\", the directory is not empty."),
  220.                                             $this->_getPath($path$name)));
  221.         }
  222.  
  223.         foreach ($file_list as $file{
  224.             if ($file['type'== '**dir'{
  225.                 $result $this->deleteFolder($this->_getPath($path$name)$file['name']$recursive);
  226.             else {
  227.                 $result $this->deleteFile($this->_getPath($path$name)$file['name']);
  228.             }
  229.             if (is_a($result'PEAR_Error')) {
  230.                 return $result;
  231.             }
  232.         }
  233.  
  234.         // Really delete the folder.
  235.         list($path$name$this->_escapeShellCommand($path$name);
  236.         $cmd = array('rmdir \"' $name '\"');
  237.         $err $this->_command($path$cmd);
  238.         if (is_a($err'PEAR_Error')) {
  239.             return PEAR::raiseError(sprintf(_("Unable to delete VFS folder \"%s\".")$this->_getPath($path$name)));
  240.         else {
  241.             return true;
  242.         }
  243.     }
  244.  
  245.     /**
  246.      * Renames a file in the VFS.
  247.      *
  248.      * @param string $oldpath  The old path to the file.
  249.      * @param string $oldname  The old filename.
  250.      * @param string $newpath  The new path of the file.
  251.      * @param string $newname  The new filename.
  252.      *
  253.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  254.      */
  255.     function rename($oldpath$oldname$newpath$newname)
  256.     {
  257.         if (is_a($result $this->autocreatePath($newpath)'PEAR_Error')) {
  258.             return $result;
  259.         }
  260.  
  261.         list($file$name$this->_escapeShellCommand($oldname$newname);
  262.         $cmd = array('rename \"' .  str_replace('/''\\\\'$oldpath'\\' $file '\" \"' .
  263.                                     str_replace('/''\\\\'$newpath'\\' $name '\"');
  264.         if (is_a($err $this->_command(''$cmd)'PEAR_Error')) {
  265.             return PEAR::raiseError(sprintf(_("Unable to rename VFS file \"%s\".")$this->_getPath($path$name)));
  266.         }
  267.  
  268.         return true;
  269.     }
  270.  
  271.     /**
  272.      * Creates a folder on the VFS.
  273.      *
  274.      * @param string $path  The path of directory to create folder.
  275.      * @param string $name  The name of the new folder.
  276.      *
  277.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  278.      */
  279.     function createFolder($path$name)
  280.     {
  281.         list($dir$mkdir$this->_escapeShellCommand($path$name);
  282.         $cmd = array('mkdir \"' $mkdir '\"');
  283.         $err $this->_command($dir$cmd);
  284.         if (is_a($err'PEAR_Error')) {
  285.             return PEAR::raiseError(sprintf(_("Unable to create VFS folder \"%s\".")$this->_getPath($path$name)));
  286.         }
  287.         return true;
  288.     }
  289.  
  290.     /**
  291.      * Returns an unsorted file list.
  292.      *
  293.      * @param string $path       The path of the directory to get the file list
  294.      *                            for.
  295.      * @param mixed $filter      Hash of items to filter based on filename.
  296.      * @param boolean $dotfiles  Show dotfiles? This is irrelevant with
  297.      *                            smbclient.
  298.      * @param boolean $dironly   Show directories only?
  299.      *
  300.      * @return boolean|PEAR_Error File list on success or a PEAR_Error on
  301.      *                              failure.
  302.      */
  303.     function listFolder($path ''$filter = null$dotfiles = true$dironly = false)
  304.     {
  305.         list($path$this->_escapeShellCommand($path);
  306.         $cmd = array('ls');
  307.         $res $this->_command($path$cmd);
  308.         if (is_a($res'PEAR_Error')) {
  309.             return $res;
  310.         }
  311.         $num_lines count($res);
  312.         $files = array();
  313.         for ($r = 0; $r $num_lines$r++{
  314.             // Match file listing.
  315.             if (preg_match('/^(\s\s.+\s{6,})/'$res[$r])) {
  316.                 // Split into columns at every six spaces
  317.                 $split1 preg_split('/\s{6,}/'trim($res[$r]));
  318.                 // If the file name isn't . or ..
  319.                 if ($split1[0!= '.' && $split1[0!= '..'{
  320.                     if (isset($split1[2])) {
  321.                         // If there is a small file size, inf could be split
  322.                         // into 3 cols.
  323.                         $split1[1.= ' ' $split1[2];
  324.                     }
  325.                     // Split file inf at every one or more spaces.
  326.                     $split2 preg_split('/\s+/'$split1[1]);
  327.                     if (is_numeric($split2[0])) {
  328.                         // If there is no file attr, shift cols over.
  329.                         array_unshift($split2'');
  330.                     }
  331.                     $my_name $split1[0];
  332.  
  333.                     // Filter out dotfiles if they aren't wanted.
  334.                     if (!$dotfiles && substr($my_name01== '.'{
  335.                         continue;
  336.                     }
  337.  
  338.                     $my_size $split2[1];
  339.                     $ext_name explode('.'$my_name);
  340.  
  341.                     if ((strpos($split2[0]'D'!== false)) {
  342.                         $my_type '**dir';
  343.                         $my_size = -1;
  344.                     else {
  345.                         $my_type VFS::strtolower($ext_name[count($ext_name- 1]);
  346.                     }
  347.                     $my_date strtotime($split2[4' ' $split2[3' ' .
  348.                                          $split2[6' ' $split2[5]);
  349.                     $filedata = array('owner' => '',
  350.                                       'group' => '',
  351.                                       'perms' => '',
  352.                                       'name' => $my_name,
  353.                                       'type' => $my_type,
  354.                                       'date' => $my_date,
  355.                                       'size' => $my_size);
  356.                     // watch for filters and dironly
  357.                     if ($this->_filterMatch($filter$my_name)) {
  358.                         unset($file);
  359.                         continue;
  360.                     }
  361.                     if ($dironly && $my_type !== '**dir'{
  362.                         unset($file);
  363.                         continue;
  364.                     }
  365.  
  366.                     $files[$filedata['name']] $filedata;
  367.                 }
  368.             }
  369.         }
  370.         return $files;
  371.     }
  372.  
  373.     /**
  374.      * Returns a sorted list of folders in specified directory.
  375.      *
  376.      * @param string $path         The path of the directory to get the
  377.      *                              directory list for.
  378.      * @param mixed $filter        Hash of items to filter based on folderlist.
  379.      * @param boolean $dotfolders  Include dotfolders? Irrelevant for SMB.
  380.      *
  381.      * @return boolean|PEAR_Error Folder list on success or a PEAR_Error on
  382.      *                              failure.
  383.      */
  384.     function listFolders($path ''$filter = null$dotfolders = true)
  385.     {
  386.         $folders = array();
  387.         $folder = array();
  388.  
  389.         $folderList $this->listFolder($pathnull$dotfolderstrue);
  390.         if (is_a($folderList'PEAR_Error')) {
  391.             return $folderList;
  392.         }
  393.  
  394.         // dirname will strip last component from path, even on a directory
  395.         $folder['val'dirname($path);
  396.         $folder['abbrev''..';
  397.         $folder['label''..';
  398.  
  399.         $folders[$folder['val']] $folder;
  400.  
  401.         foreach ($folderList as $files{
  402.             $folder['val'$this->_getPath($path$files['name']);
  403.             $folder['abbrev'$files['name'];
  404.             $folder['label'$folder['val'];
  405.  
  406.             $folders[$folder['val']] $folder;
  407.         }
  408.  
  409.         ksort($folders);
  410.         return $folders;
  411.     }
  412.  
  413.     /**
  414.      * Copies a file through the backend.
  415.      *
  416.      * @param string $path         The path to store the file in.
  417.      * @param string $name         The filename to use.
  418.      * @param string $dest         The destination of the file.
  419.      * @param boolean $autoc       reate  Automatically create directories?
  420.      *
  421.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  422.      */
  423.     function copy($path$name$dest$autocreate = false)
  424.     {
  425.         $orig $this->_getPath($path$name);
  426.         if (preg_match('|^' preg_quote($orig'[$/]|'$dest)) {
  427.             return PEAR::raiseError(_("Cannot copy file(s) - source and destination are the same."));
  428.         }
  429.  
  430.         if ($autocreate{
  431.             $result $this->autocreatePath($dest);
  432.             if (is_a($result'PEAR_Error')) {
  433.                 return $result;
  434.             }
  435.         }
  436.  
  437.         $fileCheck $this->listFolder($destnulltrue);
  438.         if (is_a($fileCheck'PEAR_Error')) {
  439.             return $fileCheck;
  440.         }
  441.         foreach ($fileCheck as $file{
  442.             if ($file['name'== $name{
  443.                 return PEAR::raiseError(sprintf(_("%s already exists."),
  444.                                                 $this->_getPath($dest$name)));
  445.             }
  446.         }
  447.  
  448.         if ($this->isFolder($path$name)) {
  449.             if (is_a($result $this->_copyRecursive($path$name$dest)'PEAR_Error')) {
  450.                 return $result;
  451.             }
  452.         else {
  453.             $tmpFile $this->_createTempFile($path$name);
  454.             if (is_a($tmpFile'PEAR_Error')) {
  455.                 return PEAR::raiseError(sprintf(_("Failed to retrieve: %s")$orig));
  456.             }
  457.  
  458.             $res $this->write($dest$name$tmpFile);
  459.             unlink($tmpFile);
  460.             if (is_a($res'PEAR_Error')) {
  461.                 return PEAR::raiseError(sprintf(_("Copy failed: %s"),
  462.                                                 $this->_getPath($dest$name)));
  463.             }
  464.         }
  465.  
  466.         return true;
  467.     }
  468.  
  469.     /**
  470.      * Moves a file through the backend.
  471.      *
  472.      * @param string $path         The path to store the file in.
  473.      * @param string $name         The filename to use.
  474.      * @param string $dest         The destination of the file.
  475.      * @param boolean $autocreate  Automatically create directories?
  476.      *
  477.      * @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
  478.      */
  479.     function move($path$name$dest$autocreate = false)
  480.     {
  481.         $orig $this->_getPath($path$name);
  482.         if (preg_match('|^' preg_quote($orig'[$/]|'$dest)) {
  483.             return PEAR::raiseError(_("Cannot move file(s) - destination is within source."));
  484.         }
  485.  
  486.         if ($autocreate{
  487.             $result $this->autocreatePath($dest);
  488.             if (is_a($result'PEAR_Error')) {
  489.                 return $result;
  490.             }
  491.         }
  492.  
  493.         $fileCheck $this->listFolder($destnulltrue);
  494.         if (is_a($fileCheck'PEAR_Error')) {
  495.             return $fileCheck;
  496.         }
  497.         foreach ($fileCheck as $file{
  498.             if ($file['name'== $name{
  499.                 return PEAR::raiseError(sprintf(_("%s already exists."),
  500.                                                 $this->_getPath($dest$name)));
  501.             }
  502.         }
  503.  
  504.         $err $this->rename($path$name$dest$name);
  505.         if (is_a($err'PEAR_Error')) {
  506.             return PEAR::raiseError(sprintf(_("Failed to move to \"%s\"."),
  507.                                             $this->_getPath($dest$name)));
  508.         }
  509.         return true;
  510.     }
  511.  
  512.     /**
  513.      * Replacement for escapeshellcmd(), variable length args, as we only want
  514.      * certain characters escaped.
  515.      *
  516.      * @access private
  517.      *
  518.      * @param array $array  Strings to escape.
  519.      *
  520.      * @return array 
  521.      */
  522.     function _escapeShellCommand()
  523.     {
  524.         $ret = array();
  525.         $args func_get_args();
  526.         foreach ($args as $arg{
  527.             $ret[str_replace(array(';''\\')array('\;''\\\\')$arg);
  528.         }
  529.         return $ret;
  530.     }
  531.  
  532.     /**
  533.      * Executes a command and returns output lines in array.
  534.      *
  535.      * @access private
  536.      *
  537.      * @param string $cmd  Command to be executed
  538.      *
  539.      * @return mixed  Array on success, false on failure.
  540.      */
  541.     function _execute($cmd)
  542.     {
  543.         $cmd str_replace('"-U%"''-N'$cmd);
  544.         exec($cmd$out$ret);
  545.  
  546.         // In some cases, (like trying to delete a nonexistant file),
  547.         // smbclient will return success (at least on 2.2.7 version I'm
  548.         // testing on). So try to match error strings, even after success.
  549.         if ($ret != 0{
  550.             $err '';
  551.             foreach ($out as $line{
  552.                 if (strpos($line'Usage:'=== 0{
  553.                     $err 'Command syntax incorrect';
  554.                     break;
  555.                 }
  556.                 if (strpos($line'ERRSRV'!== false ||
  557.                     strpos($line'ERRDOS'!== false{
  558.                     $err preg_replace('/.*\((.+)\).*/''\\1'$line);
  559.                     if (!$err{
  560.                         $err $line;
  561.                     }
  562.                     break;
  563.                 }
  564.             }
  565.             if (!$err{
  566.                 $err $out $out[count($out- 1$ret;
  567.             }
  568.             return PEAR::raiseError($err);
  569.         }
  570.  
  571.         // Check for errors even on success.
  572.         $err '';
  573.         foreach ($out as $line{
  574.             if (strpos($line'NT_STATUS_NO_SUCH_FILE'!== false ||
  575.                 strpos($line'NT_STATUS_OBJECT_NAME_NOT_FOUND'!== false{
  576.                 $err _("No such file");
  577.                 break;
  578.             elseif (strpos($line'NT_STATUS_ACCESS_DENIED'!== false{
  579.                 $err _("Permission Denied");
  580.                 break;
  581.             }
  582.         }
  583.  
  584.         if ($err{
  585.             return PEAR::raiseError($err);
  586.         }
  587.  
  588.         return $out;
  589.     }
  590.  
  591.     /**
  592.      * Executes SMB commands - without authentication - and returns output
  593.      * lines in array.
  594.      *
  595.      * @access private
  596.      *
  597.      * @param array $path  Base path for command.
  598.      * @param array $cmd   Commands to be executed.
  599.      *
  600.      * @return mixed  Array on success, false on failure.
  601.      */
  602.     function _command($path$cmd)
  603.     {
  604.         list($share$this->_escapeShellCommand($this->_params['share']);
  605.         putenv('PASSWD=' $this->_params['password']);
  606.         $ipoption (isset($this->_params['ipaddress'])) (' -I ' $this->_params['ipaddress']: null;
  607.         $fullcmd $this->_params['smbclient'.
  608.             ' "//' $this->_params['hostspec''/' $share '"' .
  609.             ' "-p' $this->_params['port''"' .
  610.             ' "-U' $this->_params['username''"' .
  611.             ' -D "' $path '" ' .
  612.             $ipoption .
  613.             ' -c "';
  614.         foreach ($cmd as $c{
  615.             $fullcmd .= $c ";";
  616.         }
  617.         $fullcmd .= '"';
  618.         return $this->_execute($fullcmd);
  619.     }
  620.  
  621.     /**
  622.      * Retrieves a file from the VFS and stores it to a temporary file.
  623.      *
  624.      * @access private
  625.      *
  626.      * @param string $path  The pathname to the file.
  627.      * @param string $name  The filename to retrieve.
  628.      *
  629.      * @return mixed  The temporary filename or a PEAR_Error on failure.
  630.      */
  631.     function _createTempFile($path$name)
  632.     {
  633.         list($path$name$this->_escapeShellCommand($path$name);
  634.         $temp $this->_getTempFile();
  635.         $cmd = array('get \"' $name '\" ' $temp);
  636.         $err $this->_command($path$cmd);
  637.         if (is_a($err'PEAR_Error')) {
  638.             return $err;
  639.         }
  640.         if (!file_exists($temp)) {
  641.             return PEAR::raiseError(sprintf(_("Unable to open VFS file \"%s\".")$this->_getPath($path$name)));
  642.         }
  643.         return $temp;
  644.     }
  645.  
  646. }

Documentation generated on Mon, 11 Mar 2019 14:39:01 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.