Advertisement
Guest User

Untitled

a guest
Mar 21st, 2017
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.38 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