Guest User

Untitled

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