Advertisement
Guest User

smbclient

a guest
Aug 1st, 2017
147
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 15.83 KB | None | 0 0
  1. <?php
  2. /**
  3.  * Class for interacting with an SMB server using the system command "smbclient".
  4.  * Of course this assumes that you have the smbclient executable installed and
  5.  * in your path.
  6.  *
  7.  * It is not the most efficient way of interacting with an SMB server -- for instance,
  8.  * putting multiple files involves running the executable multiple times and
  9.  * establishing a connection for each file.  However, if performance is not an
  10.  * issue, this is a quick-and-dirty way to move files to and from the SMB
  11.  * server from PHP.
  12.  *
  13.  * Note that this library relies heavily on the exit code from smbclient to determine
  14.  * success/failure of operations.  Unfortunately, smbclient is not very good about
  15.  * using the error codes.  A "put" command that cannot write will return an exit code
  16.  * of "1", but a "del" command on a non-existent file will exit with a status of 0.
  17.  *
  18.  * So this method is imperfect.  Better would be to actually parse the output of
  19.  * smbclient even when the exit status is 0 to see if there are codes like
  20.  * NT_STATUS_NO_SUCH_FILE or NT_STATUS_OBJECT_NAME_NOT_FOUND
  21.  */
  22. class smbclient
  23. {
  24.     // when doing "safe" puts, how many times are we willing to retry if we
  25.     // fail to send?  And how long (in ms) to wait between retries
  26.     private $_max_safe_retries = 3;
  27.     private $_safe_retry_interval = 200;
  28.  
  29.     public static $debug_mode = false;
  30.     public static $debug_label = 'smbclient';
  31.  
  32.     private $_service;
  33.     private $_username;
  34.     private $_password;
  35.    
  36.     private $_cmd;
  37.    
  38.     /**
  39.      * Gets the most recently executed command string
  40.      *
  41.      * @return string
  42.      */
  43.     public function get_last_cmd () { return $this->_cmd; }
  44.        
  45.     private $_last_cmd_stdout;
  46.     /**
  47.      * Gets stndard output from the last run command; can be useful in
  48.      * case the command reports an error; smbclient writes a lot of
  49.      * diagnostics to stdout.
  50.      *
  51.      * @return array each line of stdout is one string in the array
  52.      */
  53.     public function get_last_cmd_stdout () { return $this->_last_cmd_stdout; }
  54.    
  55.     private $_last_cmd_stderr;
  56.     /**
  57.      * Gets stndard error from the last run command
  58.      *
  59.      * @return array each line of stderr is one string in the array
  60.      */
  61.     public function get_last_cmd_stderr () { return $this->_last_cmd_stderr; }
  62.  
  63.     private $_last_cmd_exit_code;
  64.     /**
  65.      * Gets the exit code of the last command run
  66.      *
  67.      * @return int
  68.      */
  69.     public function get_last_cmd_exit_code () { return $this->_last_cmd_exit_code; }
  70.  
  71.     private $_safe_retry_count = 0;
  72.     /**
  73.      * Gets the retry count of the last safe_put() operation; if it
  74.      * succeeded on the first try, retry count is 0
  75.      *
  76.      * @return int
  77.      */
  78.     public function get_safe_retry_count () { return $this->_safe_retry_count; }
  79.    
  80.     /**
  81.      * Creates an smbclient object
  82.      *
  83.      * @param string $service the UNC service name
  84.      * @param string $username the username to use when connecting
  85.      * @param string $password the password to use when connecting
  86.      */
  87.     public function __construct ($service, $username, $password)
  88.     {
  89.         $this->_service = $service;
  90.         $this->_username = $username;
  91.         $this->_password = $password;
  92.     }
  93.    
  94.  
  95.     /**
  96.      * Gets a remote file
  97.      *
  98.      * @param string $remote_filename remote filename (use the local system's directory separators)
  99.      * @param string $local_filename the full path to the local filename
  100.      * @return bool true if successful, false otherwise
  101.      */
  102.     public function get ($remote_filename, $local_filename)
  103.     {
  104.         // convert to windows-style backslashes
  105.         $remote_filename = str_replace (DIRECTORY_SEPARATOR, '\\', $remote_filename);
  106.        
  107.         $cmd = "get \"$remote_filename\" \"$local_filename\"";
  108.        
  109.         $retval = $this->execute ($cmd);
  110.         return $retval;
  111.     }
  112.  
  113.     /**
  114.      * Puts multiple local files on the server
  115.      *
  116.      * @param array $local_files array of local filename paths
  117.      * @param string $remote_path path to remote directory (use the local system's directory separators)
  118.      * @param bool $safe use safe_put() instead of put ()
  119.      * @return bool true if successful, false otherwise
  120.      */
  121.     public function mput ($local_files, $remote_path, $safe = false)
  122.     {
  123.         foreach ($local_files as $local_file)
  124.         {
  125.             $pi = pathinfo ($local_file);
  126.            
  127.             $remote_file = $remote_path . '/' . $pi['basename'];
  128.            
  129.             if ($safe)
  130.             {
  131.                 if (!$this->safe_put ($local_file, $remote_file))
  132.                 {
  133.                     return false;
  134.                 }
  135.             }
  136.             else
  137.             {
  138.                 if (!$this->put ($local_file, $remote_file))
  139.                 {
  140.                     return false;
  141.                 }
  142.             }
  143.         }
  144.        
  145.         return true;
  146.     }
  147.  
  148.     /**
  149.      * Puts a local file
  150.      *
  151.      * @param string $local_filename the full path to local filename
  152.      * @param string $remote_filename (use the local system's directory separators)
  153.      * @return bool true if successful, false otherwise
  154.      */
  155.     public function put ($local_filename, $remote_filename)
  156.     {
  157.         // convert to windows-style backslashes
  158.         $remote_filename = str_replace (DIRECTORY_SEPARATOR, '\\', $remote_filename);
  159.        
  160.         $cmd = "put \"$local_filename\" \"$remote_filename\"";
  161.        
  162.         $retval = $this->execute ($cmd);
  163.         return $retval;
  164.     }
  165.  
  166.     /**
  167.      * Safely puts a local file; it writes to a temporary file, retrieves
  168.      * that file, compares checksum to the original local file, then if
  169.      * everything checks out, renames the remote temporary file to its
  170.      * final remote filename.
  171.      *
  172.      * @param string $local_filename the full path to local filename
  173.      * @param string $remote_filename (use the local system's directory separators)
  174.      * @return bool true if successful, false otherwise
  175.      */
  176.     public function safe_put ($local_filename, $remote_filename)
  177.     {
  178.         // I wanted to write to a temp file on the remote system, then rename the
  179.         // file, but Windows won't let you do that.  So all I can do is write to
  180.         // the permanent file, then check its contents immediately.  Two problems
  181.         // with this:
  182.         //  - if the data transfer doesn't work, I've now wrecked the file
  183.         //  - if another process writes to the file between the time I send
  184.         //    the data and the time I read it, I'm going to think the data
  185.         //    transfer failed.
  186.         //
  187.         // all the commented-out code was designed to use this strategy, before
  188.         // I found that it doesn't work.  :-(
  189.  
  190.         //$tmp_remote_filename = $remote_filename . '.' . uniqid () . '.tmp';
  191.         $tmp_local_filename = tempnam(sys_get_temp_dir(), 'safe_put');
  192.  
  193.         $local_crc = crc32 (file_get_contents ($local_filename));
  194.  
  195.         $success = false;
  196.         $this->_safe_retry_count = 0;
  197.         while (!$success)
  198.         {
  199.             self::log_msg ("retry count: " . $this->_safe_retry_count);
  200.             //if ($this->put ($local_filename, $tmp_remote_filename))
  201.             if ($this->put ($local_filename, $remote_filename))
  202.             {
  203.                 //if ($this->get ($tmp_remote_filename, $tmp_local_filename))
  204.                 if ($this->get ($remote_filename, $tmp_local_filename))
  205.                 {
  206.                     self::log_msg ("contents: '" . file_get_contents ($tmp_local_filename) . "'");
  207.                     if (crc32 (file_get_contents ($tmp_local_filename)) == $local_crc)
  208.                     {
  209.                         unlink ($tmp_local_filename);
  210.                         self::log_msg ("retrieved file matches CRC32");
  211.                         $success = true;
  212.                         return true;
  213.                         /*
  214.                         if ($this->rename ($tmp_remote_filename, $remote_filename))
  215.                         {
  216.                             $success = true;
  217.                             return true;
  218.                         }
  219.                         else
  220.                         {
  221.                             array_unshift ($this->_last_cmd_stderr,  "safe_put() failed to rename file");
  222.                         }
  223.                         */
  224.                     }
  225.                     else
  226.                     {
  227.                         self::log_msg ("retrieved file does not match CRC32");
  228.                         array_unshift ($this->_last_cmd_stderr, "safe_put() failed to validate checksum of $tmp_remote_filename");
  229.  
  230.                         /*
  231.                         if (!$this->del ($tmp_remote_filename))
  232.                         {
  233.                             array_unshift ($this->_last_cmd_stderr, "safe_put() failed to validate checksum of $tmp_remote_filename and failed to delete it from remote machine: " . $this->_last_cmd_stderr);
  234.                         }
  235.                         */
  236.                     }
  237.  
  238.                     unlink ($tmp_local_filename);
  239.                 }
  240.  
  241.             }
  242.  
  243.             if ($this->_safe_retry_count > $this->_max_safe_retries)
  244.             {
  245.                 self::log_msg ("out of retries");
  246.                 break;
  247.             }
  248.  
  249.             $this->_safe_retry_count++;
  250.             usleep ($this->_safe_retry_interval);
  251.         }
  252.  
  253.         unlink ($tmp_local_filename);
  254.         return false;
  255.     }
  256.    
  257.     /**
  258.      * Renames a remote file
  259.      *
  260.      * @param string $source_filename the remote source file (use the local system's directory separators)
  261.      * @param string $dest_filename the remote destination file (use the local system's directory separators)
  262.      * @return bool true if successful, false otherwise
  263.      */
  264.     public function rename ($source_filename, $dest_filename)
  265.     {
  266.         // convert to windows-style backslashes
  267.         $source_filename = str_replace (DIRECTORY_SEPARATOR, '\\', $source_filename);
  268.         $dest_filename = str_replace (DIRECTORY_SEPARATOR, '\\', $dest_filename);
  269.        
  270.         $cmd = "rename \"$source_filename\" \"$dest_filename\"";
  271.        
  272.         $retval = $this->execute ($cmd);
  273.         return $retval;
  274.     }
  275.    
  276.     /**
  277.      * Deletes a remote file
  278.      *
  279.      * Note:  due to limitations in smbclient, if the remote filename specifies a path,
  280.      * we can't do this in one command; instead, we need to break it into a cd and then a del.
  281.      * This is unfortunate, because if the path is specified incorrectly, and the cd fails,
  282.      * we may delete the wrong file.
  283.      *
  284.      * @param string $remote_filename (use the local system's directory separators)
  285.      * @return bool true if successful, false otherwise
  286.      */
  287.     public function del ($remote_filename)
  288.     {
  289.         $pi = pathinfo ($remote_filename);
  290.         $remote_path = $pi['dirname'];
  291.         $basename = $pi['basename'];
  292.        
  293.         // convert to windows-style backslashes
  294.         if ($remote_path)
  295.         {
  296.             $remote_path = str_replace (DIRECTORY_SEPARATOR, '\\', $remote_path);
  297.             $cmd = "cd \"$remote_path\"; del \"$basename\"";
  298.         }
  299.         else
  300.         {
  301.             $cmd = "del \"$basename\"";
  302.         }
  303.        
  304.         $retval = $this->execute ($cmd);
  305.         return $retval;
  306.     }
  307.    
  308.     /**
  309.      * Makes a directory
  310.      *
  311.      * @param string $remote_path (use the local system's directory separators)
  312.      * @return bool true if successful, false otherwise
  313.      */
  314.     public function mkdir ($remote_path)
  315.     {
  316.         $remote_path = str_replace (DIRECTORY_SEPARATOR, '\\', $remote_path);
  317.         $cmd = "mkdir \"$remote_path\"";
  318.        
  319.         $retval = $this->execute ($cmd);
  320.         return $retval;
  321.     }
  322.    
  323.    
  324.     /**
  325.      * Lists the contents of a directory on the remote server;
  326.      * Results are returned in an array of arrays.  Each subarray has the
  327.      * following hash key values:
  328.      *
  329.      * filename - name of the file
  330.      * size     - size in bytes
  331.      * mtime    - UNIX timestamp of file's modification time
  332.      * isdir    - boolean indicating whether the file is a directory
  333.      *
  334.      * Note -- parsing smbclient "dir" output is inexact.  Filenames with leading
  335.      * or trailing whitespace will lose these characters.
  336.      *
  337.      * @param string $remote_path
  338.      * @return mixed array of results if successful, false otherwise
  339.      */
  340.     public function dir ($remote_path = '', $remote_filename = '')
  341.     {
  342.         // convert to windows-style backslashes
  343.         if ($remote_path)
  344.         {
  345.             $remote_path = str_replace (DIRECTORY_SEPARATOR, '\\', $remote_path);
  346.             if ($remote_filename)
  347.             {
  348.                 $cmd = "cd \"$remote_path\"; dir \"{$remote_filename}\"";
  349.             }
  350.             else
  351.             {
  352.                 $cmd = "cd \"$remote_path\"; dir";
  353.             }
  354.         }
  355.         else
  356.         {
  357.             if ($remote_filename)
  358.             {
  359.                 $cmd = "dir \"{$remote_filename}\"";
  360.             }
  361.             else
  362.             {
  363.                 $cmd = "dir";
  364.             }
  365.         }
  366.        
  367.         $retval = $this->execute ($cmd);
  368.         if (!$retval)
  369.         {
  370.             return $retval;
  371.         }
  372.        
  373.         $xary = array ();
  374.         foreach ($this->_last_cmd_stdout as $line)
  375.         {
  376.             if (!preg_match ('#\s+(.+?)\s+(.....)\s+(\d+)\s+(\w+\s+\w+\s+\d+\s+\d\d:\d\d:\d\d\s\d+)$#', $line, $matches))
  377.             {
  378.                 continue;
  379.             }
  380.            
  381.             list ($junk, $filename, $status, $size, $mtime) = $matches;
  382.             $filename = trim ($filename);
  383.             $status = trim ($status);
  384.             $mtime = strtotime($mtime);
  385.            
  386.             $isdir = (stripos ($status, 'D') !== false) ? true : false;
  387.            
  388.             $xary[] = array ('filename' => $filename, 'size' => $size, 'mtime' => $mtime, 'isdir' => $isdir);
  389.         }
  390.        
  391.         return $xary;
  392.     }
  393.    
  394.     private function execute ($cmd)
  395.     {
  396.         $this->build_full_cmd($cmd);
  397.  
  398.         self::log_msg ($this->_cmd);
  399.        
  400.         $outfile = tempnam(".", "cmd");
  401.         $errfile = tempnam(".", "cmd");
  402.         $descriptorspec = array(
  403.             0 => array("pipe", "r"),
  404.             1 => array("file", $outfile, "w"),
  405.             2 => array("file", $errfile, "w")
  406.         );
  407.         $proc = proc_open($this->_cmd, $descriptorspec, $pipes);
  408.        
  409.         if (!is_resource($proc)) return 255;
  410.    
  411.         fclose($pipes[0]);    //Don't really want to give any input
  412.    
  413.         $exit = proc_close($proc);
  414.         $this->_last_cmd_stdout = file($outfile);
  415.         $this->_last_cmd_stderr = file($errfile);
  416.         $this->_last_cmd_exit_code = $exit;
  417.  
  418.         self::log_msg ("exit code: " . $this->_last_cmd_exit_code);
  419.         self::log_msg ("stdout: " . join ("\n", $this->_last_cmd_stdout));
  420.         self::log_msg ("stderr: " . join ("\n", $this->_last_cmd_stderr));
  421.    
  422.         unlink($outfile);
  423.         unlink($errfile);
  424.        
  425.         if ($exit)
  426.         {
  427.             return false;
  428.         }
  429.         return true;
  430.     }
  431.    
  432.     private function build_full_cmd ($cmd = '')
  433.     {
  434.         $this->_cmd = "smbclient '" . $this->_service . "'";
  435.        
  436.         $this->_cmd .= " -U '" . $this->_username . "%" . $this->_password . "'";
  437.        
  438.         if ($cmd)
  439.         {
  440.             $this->_cmd .= " -c '$cmd'";
  441.         }
  442.     }
  443.  
  444.     /**
  445.      * Logs a message if debug_mode is true and if there is a global "log_msg" function.
  446.      * @param string $msg the message to log
  447.      */
  448.     private static function log_msg ($msg)
  449.     {
  450.         if (!self::$debug_mode)
  451.         {
  452.             return;
  453.         }
  454.  
  455.         if (function_exists ('log_msg'))
  456.         {
  457.             log_msg ('[' . self::$debug_label . "] $msg");
  458.         }
  459.     }
  460. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement