PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/sources/subs/FTPConnection.class.php

https://github.com/Arantor/Elkarte
PHP | 479 lines | 269 code | 61 blank | 149 comment | 71 complexity | 58a6c07e2e74b03fe9b49be3f5cd97a4 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * The Xml_Array class is an xml parser.
  16. *
  17. */
  18. if (!defined('ELKARTE'))
  19. die('No access...');
  20. /**
  21. * Simple FTP protocol implementation.
  22. *
  23. * http://www.faqs.org/rfcs/rfc959.html
  24. */
  25. class Ftp_Connection
  26. {
  27. /**
  28. * holds the connection response
  29. * @var type
  30. */
  31. public $connection;
  32. /**
  33. * holds any errors
  34. * @var type
  35. */
  36. public $error;
  37. /**
  38. * holds last message from the server
  39. * @var type
  40. */
  41. public $last_message;
  42. /**
  43. * Passive connection
  44. * @var type
  45. */
  46. public $pasv;
  47. /**
  48. * Create a new FTP connection...
  49. *
  50. * @param type $ftp_server
  51. * @param type $ftp_port
  52. * @param type $ftp_user
  53. * @param type $ftp_pass
  54. */
  55. public function __construct($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@yourdomain.org')
  56. {
  57. // Initialize variables.
  58. $this->connection = 'no_connection';
  59. $this->error = false;
  60. $this->pasv = array();
  61. if ($ftp_server !== null)
  62. $this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass);
  63. }
  64. /**
  65. * Connects to a server
  66. *
  67. * @param type $ftp_server
  68. * @param type $ftp_port
  69. * @param type $ftp_user
  70. * @param type $ftp_pass
  71. * @return type
  72. */
  73. public function connect($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@yourdomain.org')
  74. {
  75. if (strpos($ftp_server, 'ftp://') === 0)
  76. $ftp_server = substr($ftp_server, 6);
  77. elseif (strpos($ftp_server, 'ftps://') === 0)
  78. $ftp_server = 'ssl://' . substr($ftp_server, 7);
  79. if (strpos($ftp_server, 'http://') === 0)
  80. $ftp_server = substr($ftp_server, 7);
  81. $ftp_server = strtr($ftp_server, array('/' => '', ':' => '', '@' => ''));
  82. // Connect to the FTP server.
  83. $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5);
  84. if (!$this->connection)
  85. {
  86. $this->error = 'bad_server';
  87. return;
  88. }
  89. // Get the welcome message...
  90. if (!$this->check_response(220))
  91. {
  92. $this->error = 'bad_response';
  93. return;
  94. }
  95. // Send the username, it should ask for a password.
  96. fwrite($this->connection, 'USER ' . $ftp_user . "\r\n");
  97. if (!$this->check_response(331))
  98. {
  99. $this->error = 'bad_username';
  100. return;
  101. }
  102. // Now send the password... and hope it goes okay.
  103. fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n");
  104. if (!$this->check_response(230))
  105. {
  106. $this->error = 'bad_password';
  107. return;
  108. }
  109. }
  110. /**
  111. * Changes to a directory (chdir) via the ftp connection
  112. *
  113. * @param type $ftp_path
  114. * @return boolean
  115. */
  116. public function chdir($ftp_path)
  117. {
  118. if (!is_resource($this->connection))
  119. return false;
  120. // No slash on the end, please...
  121. if ($ftp_path !== '/' && substr($ftp_path, -1) === '/')
  122. $ftp_path = substr($ftp_path, 0, -1);
  123. fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n");
  124. if (!$this->check_response(250))
  125. {
  126. $this->error = 'bad_path';
  127. return false;
  128. }
  129. return true;
  130. }
  131. /**
  132. * Changes a files atrributes (chmod)
  133. *
  134. * @param string $ftp_file
  135. * @param type $chmod
  136. * @return boolean
  137. */
  138. public function chmod($ftp_file, $chmod)
  139. {
  140. if (!is_resource($this->connection))
  141. return false;
  142. if ($ftp_file == '')
  143. $ftp_file = '.';
  144. // Convert the chmod value from octal (0777) to text ("777").
  145. fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n");
  146. if (!$this->check_response(200))
  147. {
  148. $this->error = 'bad_file';
  149. return false;
  150. }
  151. return true;
  152. }
  153. /**
  154. * Deletes a file
  155. *
  156. * @param type $ftp_file
  157. * @return boolean
  158. */
  159. public function unlink($ftp_file)
  160. {
  161. // We are actually connected, right?
  162. if (!is_resource($this->connection))
  163. return false;
  164. // Delete file X.
  165. fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n");
  166. if (!$this->check_response(250))
  167. {
  168. fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n");
  169. // Still no love?
  170. if (!$this->check_response(250))
  171. {
  172. $this->error = 'bad_file';
  173. return false;
  174. }
  175. }
  176. return true;
  177. }
  178. /**
  179. * Reads the response to the command from the server
  180. *
  181. * @param type $desired
  182. * @return type
  183. */
  184. public function check_response($desired)
  185. {
  186. // Wait for a response that isn't continued with -, but don't wait too long.
  187. $time = time();
  188. do
  189. $this->last_message = fgets($this->connection, 1024);
  190. while ((strlen($this->last_message) < 4 || strpos($this->last_message, ' ') === 0 || strpos($this->last_message, ' ', 3) !== 3) && time() - $time < 5);
  191. // Was the desired response returned?
  192. return is_array($desired) ? in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired;
  193. }
  194. /**
  195. * Used to create a passive connection
  196. *
  197. * @return boolean
  198. */
  199. public function passive()
  200. {
  201. // We can't create a passive data connection without a primary one first being there.
  202. if (!is_resource($this->connection))
  203. return false;
  204. // Request a passive connection - this means, we'll talk to you, you don't talk to us.
  205. @fwrite($this->connection, 'PASV' . "\r\n");
  206. $time = time();
  207. do
  208. $response = fgets($this->connection, 1024);
  209. while (strpos($response, ' ', 3) !== 3 && time() - $time < 5);
  210. // If it's not 227, we weren't given an IP and port, which means it failed.
  211. if (strpos($response, '227 ') !== 0)
  212. {
  213. $this->error = 'bad_response';
  214. return false;
  215. }
  216. // Snatch the IP and port information, or die horribly trying...
  217. if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0)
  218. {
  219. $this->error = 'bad_response';
  220. return false;
  221. }
  222. // This is pretty simple - store it for later use ;).
  223. $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]);
  224. return true;
  225. }
  226. /**
  227. * Creates a new file on the server
  228. *
  229. * @param type $ftp_file
  230. * @return boolean
  231. */
  232. public function create_file($ftp_file)
  233. {
  234. // First, we have to be connected... very important.
  235. if (!is_resource($this->connection))
  236. return false;
  237. // I'd like one passive mode, please!
  238. if (!$this->passive())
  239. return false;
  240. // Seems logical enough, so far...
  241. fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n");
  242. // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc.
  243. $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5);
  244. if (!$fp || !$this->check_response(150))
  245. {
  246. $this->error = 'bad_file';
  247. @fclose($fp);
  248. return false;
  249. }
  250. // This may look strange, but we're just closing it to indicate a zero-byte upload.
  251. fclose($fp);
  252. if (!$this->check_response(226))
  253. {
  254. $this->error = 'bad_response';
  255. return false;
  256. }
  257. return true;
  258. }
  259. /**
  260. * Generates a direcotry listing for the current directory
  261. *
  262. * @param type $ftp_path
  263. * @param type $search
  264. * @return boolean
  265. */
  266. public function list_dir($ftp_path = '', $search = false)
  267. {
  268. // Are we even connected...?
  269. if (!is_resource($this->connection))
  270. return false;
  271. // Passive... non-agressive...
  272. if (!$this->passive())
  273. return false;
  274. // Get the listing!
  275. fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n");
  276. // Connect, assuming we've got a connection.
  277. $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5);
  278. if (!$fp || !$this->check_response(array(150, 125)))
  279. {
  280. $this->error = 'bad_response';
  281. @fclose($fp);
  282. return false;
  283. }
  284. // Read in the file listing.
  285. $data = '';
  286. while (!feof($fp))
  287. $data .= fread($fp, 4096);
  288. fclose($fp);
  289. // Everything go okay?
  290. if (!$this->check_response(226))
  291. {
  292. $this->error = 'bad_response';
  293. return false;
  294. }
  295. return $data;
  296. }
  297. /**
  298. * Determins the current dirctory we are in
  299. *
  300. * @param type $file
  301. * @param type $listing
  302. * @return string|boolean
  303. */
  304. public function locate($file, $listing = null)
  305. {
  306. if ($listing === null)
  307. $listing = $this->list_dir('', true);
  308. $listing = explode("\n", $listing);
  309. @fwrite($this->connection, 'PWD' . "\r\n");
  310. $time = time();
  311. do
  312. $response = fgets($this->connection, 1024);
  313. while ($response[3] != ' ' && time() - $time < 5);
  314. // Check for 257!
  315. if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0)
  316. $current_dir = strtr($match[1], array('""' => '"'));
  317. else
  318. $current_dir = '';
  319. for ($i = 0, $n = count($listing); $i < $n; $i++)
  320. {
  321. if (trim($listing[$i]) == '' && isset($listing[$i + 1]))
  322. {
  323. $current_dir = substr(trim($listing[++$i]), 0, -1);
  324. $i++;
  325. }
  326. // Okay, this file's name is:
  327. $listing[$i] = $current_dir . '/' . trim(strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]);
  328. if ($file[0] == '*' && substr($listing[$i], -(strlen($file) - 1)) == substr($file, 1))
  329. return $listing[$i];
  330. if (substr($file, -1) == '*' && substr($listing[$i], 0, strlen($file) - 1) == substr($file, 0, -1))
  331. return $listing[$i];
  332. if (basename($listing[$i]) == $file || $listing[$i] == $file)
  333. return $listing[$i];
  334. }
  335. return false;
  336. }
  337. /**
  338. * Creates a new directory on the server
  339. *
  340. * @param type $ftp_dir
  341. * @return boolean
  342. */
  343. public function create_dir($ftp_dir)
  344. {
  345. // We must be connected to the server to do something.
  346. if (!is_resource($this->connection))
  347. return false;
  348. // Make this new beautiful directory!
  349. fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n");
  350. if (!$this->check_response(257))
  351. {
  352. $this->error = 'bad_file';
  353. return false;
  354. }
  355. return true;
  356. }
  357. /**
  358. * Detects the current path
  359. *
  360. * @param type $filesystem_path
  361. * @param type $lookup_file
  362. * @return type
  363. */
  364. public function detect_path($filesystem_path, $lookup_file = null)
  365. {
  366. $username = '';
  367. if (isset($_SERVER['DOCUMENT_ROOT']))
  368. {
  369. if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match))
  370. {
  371. $username = $match[1];
  372. $path = strtr($_SERVER['DOCUMENT_ROOT'], array('/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => ''));
  373. if (substr($path, -1) == '/')
  374. $path = substr($path, 0, -1);
  375. if (strlen(dirname($_SERVER['PHP_SELF'])) > 1)
  376. $path .= dirname($_SERVER['PHP_SELF']);
  377. }
  378. elseif (strpos($filesystem_path, '/var/www/') === 0)
  379. $path = substr($filesystem_path, 8);
  380. else
  381. $path = strtr(strtr($filesystem_path, array('\\' => '/')), array($_SERVER['DOCUMENT_ROOT'] => ''));
  382. }
  383. else
  384. $path = '';
  385. if (is_resource($this->connection) && $this->list_dir($path) == '')
  386. {
  387. $data = $this->list_dir('', true);
  388. if ($lookup_file === null)
  389. $lookup_file = $_SERVER['PHP_SELF'];
  390. $found_path = dirname($this->locate('*' . basename(dirname($lookup_file)) . '/' . basename($lookup_file), $data));
  391. if ($found_path == false)
  392. $found_path = dirname($this->locate(basename($lookup_file)));
  393. if ($found_path != false)
  394. $path = $found_path;
  395. }
  396. elseif (is_resource($this->connection))
  397. $found_path = true;
  398. return array($username, $path, isset($found_path));
  399. }
  400. /**
  401. * Close the ftp connection
  402. *
  403. * @return boolean
  404. */
  405. public function close()
  406. {
  407. // Goodbye!
  408. fwrite($this->connection, 'QUIT' . "\r\n");
  409. fclose($this->connection);
  410. return true;
  411. }
  412. }